git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

Cocoa/GBView.m

      1 #import <IOKit/pwr_mgt/IOPMLib.h>
      2 #import <Carbon/Carbon.h>
      3 #import "GBView.h"
      4 #import "GBViewGL.h"
      5 #import "GBViewMetal.h"
      6 #import "GBButtons.h"
      7 #import "NSString+StringForKey.h"
      8 #import "NSObject+DefaultsObserver.h"
      9 #import "Document.h"
     10 
     11 #define JOYSTICK_HIGH 0x4000
     12 #define JOYSTICK_LOW 0x3800
     13 
     14 static const uint8_t workboy_ascii_to_key[] = {
     15     ['0'] = GB_WORKBOY_0,
     16     ['`'] = GB_WORKBOY_UMLAUT,
     17     ['1'] = GB_WORKBOY_1,
     18     ['2'] = GB_WORKBOY_2,
     19     ['3'] = GB_WORKBOY_3,
     20     ['4'] = GB_WORKBOY_4,
     21     ['5'] = GB_WORKBOY_5,
     22     ['6'] = GB_WORKBOY_6,
     23     ['7'] = GB_WORKBOY_7,
     24     ['8'] = GB_WORKBOY_8,
     25     ['9'] = GB_WORKBOY_9,
     26     
     27     ['\r'] = GB_WORKBOY_ENTER,
     28     [3] = GB_WORKBOY_ENTER,
     29     
     30     ['!'] = GB_WORKBOY_EXCLAMATION_MARK,
     31     ['$'] = GB_WORKBOY_DOLLAR,
     32     ['#'] = GB_WORKBOY_HASH,
     33     ['~'] = GB_WORKBOY_TILDE,
     34     ['*'] = GB_WORKBOY_ASTERISK,
     35     ['+'] = GB_WORKBOY_PLUS,
     36     ['-'] = GB_WORKBOY_MINUS,
     37     ['('] = GB_WORKBOY_LEFT_PARENTHESIS,
     38     [')'] = GB_WORKBOY_RIGHT_PARENTHESIS,
     39     [';'] = GB_WORKBOY_SEMICOLON,
     40     [':'] = GB_WORKBOY_COLON,
     41     ['%'] = GB_WORKBOY_PERCENT,
     42     ['='] = GB_WORKBOY_EQUAL,
     43     [','] = GB_WORKBOY_COMMA,
     44     ['<'] = GB_WORKBOY_LT,
     45     ['.'] = GB_WORKBOY_DOT,
     46     ['>'] = GB_WORKBOY_GT,
     47     ['/'] = GB_WORKBOY_SLASH,
     48     ['?'] = GB_WORKBOY_QUESTION_MARK,
     49     [' '] = GB_WORKBOY_SPACE,
     50     ['\''] = GB_WORKBOY_QUOTE,
     51     ['@'] = GB_WORKBOY_AT,
     52     
     53     ['q'] = GB_WORKBOY_Q,
     54     ['w'] = GB_WORKBOY_W,
     55     ['e'] = GB_WORKBOY_E,
     56     ['r'] = GB_WORKBOY_R,
     57     ['t'] = GB_WORKBOY_T,
     58     ['y'] = GB_WORKBOY_Y,
     59     ['u'] = GB_WORKBOY_U,
     60     ['i'] = GB_WORKBOY_I,
     61     ['o'] = GB_WORKBOY_O,
     62     ['p'] = GB_WORKBOY_P,
     63     ['a'] = GB_WORKBOY_A,
     64     ['s'] = GB_WORKBOY_S,
     65     ['d'] = GB_WORKBOY_D,
     66     ['f'] = GB_WORKBOY_F,
     67     ['g'] = GB_WORKBOY_G,
     68     ['h'] = GB_WORKBOY_H,
     69     ['j'] = GB_WORKBOY_J,
     70     ['k'] = GB_WORKBOY_K,
     71     ['l'] = GB_WORKBOY_L,
     72     ['z'] = GB_WORKBOY_Z,
     73     ['x'] = GB_WORKBOY_X,
     74     ['c'] = GB_WORKBOY_C,
     75     ['v'] = GB_WORKBOY_V,
     76     ['b'] = GB_WORKBOY_B,
     77     ['n'] = GB_WORKBOY_N,
     78     ['m'] = GB_WORKBOY_M,
     79 };
     80 
     81 static const uint8_t workboy_vk_to_key[] = {
     82     [kVK_F1] = GB_WORKBOY_CLOCK,
     83     [kVK_F2] = GB_WORKBOY_TEMPERATURE,
     84     [kVK_F3] = GB_WORKBOY_MONEY,
     85     [kVK_F4] = GB_WORKBOY_CALCULATOR,
     86     [kVK_F5] = GB_WORKBOY_DATE,
     87     [kVK_F6] = GB_WORKBOY_CONVERSION,
     88     [kVK_F7] = GB_WORKBOY_RECORD,
     89     [kVK_F8] = GB_WORKBOY_WORLD,
     90     [kVK_F9] = GB_WORKBOY_PHONE,
     91     [kVK_F10] = GB_WORKBOY_UNKNOWN,
     92     [kVK_Delete] = GB_WORKBOY_BACKSPACE,
     93     [kVK_Shift] = GB_WORKBOY_SHIFT_DOWN,
     94     [kVK_RightShift] = GB_WORKBOY_SHIFT_DOWN,
     95     [kVK_UpArrow] = GB_WORKBOY_UP,
     96     [kVK_DownArrow] = GB_WORKBOY_DOWN,
     97     [kVK_LeftArrow] = GB_WORKBOY_LEFT,
     98     [kVK_RightArrow] = GB_WORKBOY_RIGHT,
     99     [kVK_Escape] = GB_WORKBOY_ESCAPE,
    100     [kVK_ANSI_KeypadDecimal] = GB_WORKBOY_DECIMAL_POINT,
    101     [kVK_ANSI_KeypadClear] = GB_WORKBOY_M,
    102     [kVK_ANSI_KeypadMultiply] = GB_WORKBOY_H,
    103     [kVK_ANSI_KeypadDivide] = GB_WORKBOY_J,
    104 };
    105 
    106 @implementation GBView
    107 {
    108     bool mouse_hidden;
    109     NSTrackingArea *tracking_area;
    110     bool _mouseHidingEnabled;
    111     bool axisActive[2];
    112     bool underclockKeyDown;
    113     double clockMultiplier;
    114     double analogClockMultiplier;
    115     bool analogClockMultiplierValid;
    116     NSEventModifierFlags previousModifiers;
    117     JOYController *lastController;
    118     bool _turbo;
    119     bool _mouseControlEnabled;
    120     NSMutableDictionary<NSNumber *, JOYController *> *_controllerMapping;
    121     unsigned _lastPlayerCount;
    122     
    123     bool _rapidA[4], _rapidB[4];
    124     uint8_t _rapidACount[4], _rapidBCount[4];
    125 }
    126 
    127 + (instancetype)alloc
    128 {
    129     return [self allocWithZone:NULL];
    130 }
    131 
    132 + (instancetype)allocWithZone:(struct _NSZone *)zone
    133 {
    134     if (self == [GBView class]) {
    135         if ([GBViewMetal isSupported]) {
    136             return [GBViewMetal allocWithZone: zone];
    137         }
    138         return [GBViewGL allocWithZone: zone];
    139     }
    140     return [super allocWithZone:zone];
    141 }
    142 
    143 - (void) _init
    144 {
    145     [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
    146     
    147     __unsafe_unretained GBView *weakSelf = self;
    148     [self observeStandardDefaultsKey:@"GBAspectRatioUnkept" withBlock:^(id newValue) {
    149         [weakSelf setFrame:weakSelf.superview.frame];
    150     }];
    151     [self observeStandardDefaultsKey:@"GBForceIntegerScale" withBlock:^(id newValue) {
    152         [weakSelf setFrame:weakSelf.superview.frame];
    153     }];
    154     [self observeStandardDefaultsKey:@"JoyKitDefaultControllers" withBlock:^(id newValue) {
    155         [weakSelf reassignControllers];
    156     }];
    157     tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}
    158                                                   options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseMoved
    159                                                     owner:self
    160                                                  userInfo:nil];
    161     [self addTrackingArea:tracking_area];
    162     clockMultiplier = 1.0;
    163     [self createInternalView];
    164     [self addSubview:self.internalView];
    165     self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
    166     [JOYController registerListener:self];
    167     _mouseControlEnabled = true;
    168     [self reassignControllers];
    169 }
    170 
    171 - (void)controllerConnected:(JOYController *)controller
    172 {
    173     [self reassignControllers];
    174 }
    175 
    176 - (void)controllerDisconnected:(JOYController *)controller
    177 {
    178     [self reassignControllers];
    179 }
    180 
    181 - (unsigned)playerCount
    182 {
    183     if (self.document.partner) {
    184         return 2;
    185     }
    186     if (!_gb) {
    187         return 1;
    188     }
    189     return GB_get_player_count(_gb);
    190 }
    191 
    192 - (void)reassignControllers
    193 {
    194     unsigned playerCount = self.playerCount;
    195     /* Don't assign controlelrs if there's only one player, allow all controllers. */
    196     if (playerCount == 1) {
    197         _controllerMapping = [NSMutableDictionary dictionary];
    198         return;
    199     }
    200     
    201     if (!_controllerMapping) {
    202         _controllerMapping = [NSMutableDictionary dictionary];
    203     }
    204     
    205     for (NSNumber *player in [_controllerMapping copy]) {
    206         if (player.unsignedIntValue >= playerCount || !_controllerMapping[player].connected) {
    207             [_controllerMapping removeObjectForKey:player];
    208         }
    209     }
    210     
    211     _lastPlayerCount = playerCount;
    212     for (unsigned i = 0; i < playerCount; i++) {
    213         NSString *preferredJoypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"]
    214                                       objectForKey:n2s(i)];
    215         for (JOYController *controller in [JOYController allControllers]) {
    216             if (!controller.connected) continue;
    217             if ([controller.uniqueID isEqual:preferredJoypad]) {
    218                 _controllerMapping[@(i)] = controller;
    219                 break;
    220             }
    221         }
    222     }
    223 }
    224 
    225 - (void)tryAssigningController:(JOYController *)controller
    226 {
    227     unsigned playerCount = self.playerCount;
    228     if (playerCount == 1) return;
    229     if (_controllerMapping.count == playerCount) return;
    230     if ([_controllerMapping.allValues containsObject:controller]) return;
    231     for (unsigned i = 0; i < playerCount; i++) {
    232         if (!_controllerMapping[@(i)]) {
    233             _controllerMapping[@(i)] = controller;
    234             return;
    235         }
    236     }
    237 }
    238 
    239 - (NSDictionary<NSNumber *, JOYController *> *)controllerMapping
    240 {
    241     if (_lastPlayerCount != self.playerCount) {
    242         [self reassignControllers];
    243     }
    244     
    245     return _controllerMapping;
    246 }
    247 
    248 - (void)screenSizeChanged
    249 {
    250     [super screenSizeChanged];
    251     
    252     dispatch_async(dispatch_get_main_queue(), ^{
    253         [self setFrame:self.superview.frame];
    254     });
    255 }
    256 
    257 - (void)dealloc
    258 {
    259     if (mouse_hidden) {
    260         mouse_hidden = false;
    261         [NSCursor unhide];
    262     }
    263     [[NSNotificationCenter defaultCenter] removeObserver:self];
    264     [self setRumble:0];
    265     [JOYController unregisterListener:self];
    266 }
    267 
    268 - (instancetype)initWithCoder:(NSCoder *)coder
    269 {
    270     if (!(self = [super initWithCoder:coder])) { 
    271         return self;
    272     }
    273     [self _init];
    274     return self;
    275 }
    276 
    277 - (instancetype)initWithFrame:(NSRect)frameRect
    278 {
    279     if (!(self = [super initWithFrame:frameRect])) { 
    280         return self;
    281     }
    282     [self _init];
    283     return self;
    284 }
    285 
    286 - (void)setFrame:(NSRect)frame
    287 {
    288     NSView *superview = self.superview;
    289     if (GB_unlikely(!superview)) return;
    290     frame = superview.frame;
    291     if (_gb && ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) {
    292         double ratio = frame.size.width / frame.size.height;
    293         double width = GB_get_screen_width(_gb);
    294         double height = GB_get_screen_height(_gb);
    295         if (ratio >= width / height) {
    296             double new_width = round(frame.size.height / height * width);
    297             frame.origin.x = floor((frame.size.width - new_width) / 2);
    298             frame.size.width = new_width;
    299             frame.origin.y = 0;
    300         }
    301         else {
    302             double new_height = round(frame.size.width / width * height);
    303             frame.origin.y = floor((frame.size.height - new_height) / 2);
    304             frame.size.height = new_height;
    305             frame.origin.x = 0;
    306         }
    307     }
    308     
    309     if (_gb && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBForceIntegerScale"]) {
    310         double factor = self.window.backingScaleFactor;
    311         double width = GB_get_screen_width(_gb) / factor;
    312         double height = GB_get_screen_height(_gb) / factor;
    313         
    314         double new_width = floor(frame.size.width / width)  * width;
    315         double new_height = floor(frame.size.height / height)  * height;
    316         frame.origin.x += floor((frame.size.width - new_width) / 2);
    317         frame.origin.y += floor((frame.size.height - new_height) / 2);
    318         frame.size.width = new_width;
    319         frame.size.height = new_height;
    320     }
    321 
    322     [super setFrame:frame];
    323 }
    324 
    325 - (void) flip
    326 {
    327     if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) {
    328         clockMultiplier = 1.0;
    329         GB_set_clock_multiplier(_gb, analogClockMultiplier);
    330         if (self.document.partner) {
    331             GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier);
    332         }
    333         if (analogClockMultiplier == 1.0) {
    334             analogClockMultiplierValid = false;
    335         }
    336         if (analogClockMultiplier < 2.0 && analogClockMultiplier > 1.0) {
    337             GB_set_turbo_mode(_gb, false, false);
    338             if (self.document.partner) {
    339                 GB_set_turbo_mode(self.document.partner.gb, false, false);
    340             }
    341         }
    342     }
    343     else {
    344         if (underclockKeyDown && clockMultiplier > 0.5) {
    345             clockMultiplier -= 1.0/16;
    346             GB_set_clock_multiplier(_gb, clockMultiplier);
    347             if (self.document.partner) {
    348                 GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
    349             }
    350         }
    351         if (!underclockKeyDown && clockMultiplier < 1.0) {
    352             clockMultiplier += 1.0/16;
    353             GB_set_clock_multiplier(_gb, clockMultiplier);
    354             if (self.document.partner) {
    355                 GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
    356             }
    357         }
    358     }
    359     if ((!analogClockMultiplierValid && clockMultiplier > 1) ||
    360         _turbo || (analogClockMultiplierValid && analogClockMultiplier > 1)) {
    361         [self.osdView displayText:@"Fast forwarding…"];
    362     }
    363     else if ((!analogClockMultiplierValid && clockMultiplier < 1) ||
    364              (analogClockMultiplierValid && analogClockMultiplier < 1)) {
    365         [self.osdView displayText:@"Slow motion…"];
    366     }
    367     
    368     for (unsigned i = GB_get_player_count(_gb); i--;) {
    369         if (_rapidA[i]) {
    370             _rapidACount[i]++;
    371             GB_set_key_state_for_player(_gb, GB_KEY_A, i, !(_rapidACount[i] & 2));
    372         }
    373         if (_rapidB[i]) {
    374             _rapidBCount[i]++;
    375             GB_set_key_state_for_player(_gb, GB_KEY_B, i, !(_rapidBCount[i] & 2));
    376         }
    377     }
    378     [super flip];
    379 }
    380 
    381 -(void)keyDown:(NSEvent *)theEvent
    382 {
    383     if ([theEvent type] != NSEventTypeFlagsChanged && theEvent.isARepeat) return;
    384     unsigned short keyCode = theEvent.keyCode;
    385     if (GB_workboy_is_enabled(_gb)) {
    386         if (theEvent.keyCode < sizeof(workboy_vk_to_key) && workboy_vk_to_key[theEvent.keyCode]) {
    387             GB_workboy_set_key(_gb, workboy_vk_to_key[theEvent.keyCode]);
    388             return;
    389         }
    390         unichar c = [theEvent type] != NSEventTypeFlagsChanged? [theEvent.charactersIgnoringModifiers.lowercaseString characterAtIndex:0] : 0;
    391         if (c < sizeof(workboy_ascii_to_key) && workboy_ascii_to_key[c]) {
    392             GB_workboy_set_key(_gb, workboy_ascii_to_key[c]);
    393             return;
    394         }
    395     }
    396     
    397     bool handled = false;
    398 
    399     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    400     unsigned player_count = GB_get_player_count(_gb);
    401     if (self.document.partner) {
    402         player_count = 2;
    403     }
    404     for (unsigned player = 0; player < player_count; player++) {
    405         for (GBButton button = 0; button < GBKeyboardButtonCount; button++) {
    406             NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
    407             if (!key) continue;
    408 
    409             if (key.unsignedShortValue == keyCode) {
    410                 handled = true;
    411                 switch (button) {
    412                     case GBTurbo:
    413                         if (self.document.isSlave) {
    414                             GB_set_turbo_mode(self.document.partner.gb, true, false);
    415                         }
    416                         else {
    417                             GB_set_turbo_mode(_gb, true, self.isRewinding);
    418                         }
    419                         _turbo = true;
    420                         analogClockMultiplierValid = false;
    421                         break;
    422                         
    423                     case GBRewind:
    424                         if (!self.document.partner) {
    425                             self.isRewinding = true;
    426                             GB_set_turbo_mode(_gb, false, false);
    427                             _turbo = false;
    428                         }
    429                         break;
    430                         
    431                     case GBUnderclock:
    432                         underclockKeyDown = true;
    433                         analogClockMultiplierValid = false;
    434                         break;
    435                         
    436                     case GBRapidA:
    437                         _rapidA[player] = true;
    438                         _rapidACount[player] = 0;
    439                         GB_set_key_state_for_player(_gb, GB_KEY_A, player, true);
    440                         break;
    441                         
    442                     case GBRapidB:
    443                         _rapidB[player] = true;
    444                         _rapidBCount[player] = 0;
    445                         GB_set_key_state_for_player(_gb, GB_KEY_B, player, true);
    446                         break;
    447                         
    448                     default:
    449                         if (self.document.partner) {
    450                             if (player == 0) {
    451                                 GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, true);
    452                                 if ((GB_key_t)button <= GB_KEY_DOWN) {
    453                                     GB_set_use_faux_analog_inputs(_gb, 0, false);
    454                                 }
    455                             }
    456                             else {
    457                                 GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, true);
    458                                 if ((GB_key_t)button <= GB_KEY_DOWN) {
    459                                     GB_set_use_faux_analog_inputs(self.document.partner.gb, 0, false);
    460                                 }
    461                             }
    462                         }
    463                         else {
    464                             GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
    465                             if ((GB_key_t)button <= GB_KEY_DOWN) {
    466                                 GB_set_use_faux_analog_inputs(_gb, player, false);
    467                             }
    468                         }
    469                         break;
    470                 }
    471             }
    472         }
    473     }
    474 
    475     if (!handled && [theEvent type] != NSEventTypeFlagsChanged) {
    476         [super keyDown:theEvent];
    477     }
    478 }
    479 
    480 -(void)keyUp:(NSEvent *)theEvent
    481 {
    482     unsigned short keyCode = theEvent.keyCode;
    483     if (GB_workboy_is_enabled(_gb)) {
    484         if (keyCode == kVK_Shift || keyCode == kVK_RightShift) {
    485             GB_workboy_set_key(_gb, GB_WORKBOY_SHIFT_UP);
    486         }
    487         else {
    488             GB_workboy_set_key(_gb, GB_WORKBOY_NONE);
    489         }
    490 
    491     }
    492     bool handled = false;
    493 
    494     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    495     unsigned player_count = GB_get_player_count(_gb);
    496     if (self.document.partner) {
    497         player_count = 2;
    498     }
    499     for (unsigned player = 0; player < player_count; player++) {
    500         for (GBButton button = 0; button < GBKeyboardButtonCount; button++) {
    501             NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
    502             if (!key) continue;
    503             
    504             if (key.unsignedShortValue == keyCode) {
    505                 handled = true;
    506                 switch (button) {
    507                     case GBTurbo:
    508                         if (self.document.isSlave) {
    509                             GB_set_turbo_mode(self.document.partner.gb, false, false);
    510                         }
    511                         else {
    512                             GB_set_turbo_mode(_gb, false, false);
    513                         }
    514                         _turbo = false;
    515                         analogClockMultiplierValid = false;
    516                         break;
    517                         
    518                     case GBRewind:
    519                         self.isRewinding = false;
    520                         break;
    521                         
    522                     case GBUnderclock:
    523                         underclockKeyDown = false;
    524                         analogClockMultiplierValid = false;
    525                         break;
    526                     
    527                     case GBRapidA:
    528                         _rapidA[player] = false;
    529                         GB_set_key_state_for_player(_gb, GB_KEY_A, player, false);
    530                         break;
    531                         
    532                     case GBRapidB:
    533                         _rapidB[player] = false;
    534                         GB_set_key_state_for_player(_gb, GB_KEY_B, player, false);
    535                         break;
    536                         
    537                     default:
    538                         if (self.document.partner) {
    539                             if (player == 0) {
    540                                 GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, false);
    541                             }
    542                             else {
    543                                 GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, false);
    544                             }
    545                         }
    546                         else {
    547                             GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
    548                         }
    549                         break;
    550                 }
    551             }
    552         }
    553     }
    554     if (!handled && [theEvent type] != NSEventTypeFlagsChanged) {
    555         [super keyUp:theEvent];
    556     }
    557 }
    558 
    559 - (void)setRumble:(double)amp
    560 {
    561     double strength = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBRumbleStrength"];
    562     if (strength != 1) {
    563         amp = pow(amp, strength) * strength;
    564     }
    565     [lastController setRumbleAmplitude:amp];
    566 }
    567 
    568 - (bool)shouldControllerUseJoystickForMotion:(JOYController *)controller
    569 {
    570     if (!_gb) return false;
    571     if (!GB_has_accelerometer(_gb)) return false;
    572     if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return true;
    573     for (JOYAxes3D *axes in controller.axes3D) {
    574         if (axes.usage == JOYAxes3DUsageOrientation || axes.usage == JOYAxes3DUsageAcceleration) {
    575             return false;
    576         }
    577     }
    578     return true;
    579 }
    580 
    581 - (bool)allowController
    582 {
    583     if ([self.window isMainWindow]) return true;
    584     if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"]) {
    585         if ([(Document *)[NSApplication sharedApplication].orderedDocuments.firstObject mainWindow] == self.window) {
    586             return true;
    587         }
    588     }
    589     return false;
    590 }
    591 
    592 - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis
    593 {
    594     if (!_gb) return;
    595     if (![self allowController]) return;
    596 
    597     NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
    598     if (!mapping) {
    599         mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
    600     }
    601     
    602     if ((axis.usage == JOYAxisUsageR1 && !mapping) ||
    603         axis.uniqueID == [mapping[@"AnalogUnderclock"] unsignedLongValue]){
    604         analogClockMultiplier = MIN(MAX(1 - axis.value + 0.05, 1.0 / 3), 1.0);
    605         analogClockMultiplierValid = true;
    606     }
    607     
    608     else if ((axis.usage == JOYAxisUsageL1 && !mapping) ||
    609         axis.uniqueID == [mapping[@"AnalogTurbo"] unsignedLongValue]){
    610         analogClockMultiplier = MIN(MAX(axis.value * 3 + 0.95, 1.0), 3.0);
    611         analogClockMultiplierValid = true;
    612     }
    613 }
    614 
    615 - (bool)controller:(JOYController *)controller applicableForPlayer:(unsigned)player effectivePlayer:(unsigned *)effectivePlayer effectiveGB:(GB_gameboy_t **)effectiveGB
    616 {
    617     NSDictionary<NSNumber *, JOYController *> *controllerMapping = [self controllerMapping];
    618 
    619     JOYController *preferredJoypad = controllerMapping[@(player)];
    620     if (preferredJoypad && preferredJoypad != controller) return false; // The player has a different assigned controller
    621     if (!preferredJoypad && self.playerCount != 1) return false; // The player has no assigned controller in multiplayer mode, prevent controller inputs
    622     
    623     dispatch_async(dispatch_get_main_queue(), ^{
    624         [controller setPlayerLEDs:[controller LEDMaskForPlayer:player]];
    625     });
    626     
    627     *effectiveGB = _gb;
    628     *effectivePlayer = player;
    629     
    630     if (player && self.document.partner) {
    631         *effectiveGB = self.document.partner.gb;
    632         *effectivePlayer = 0;
    633         if (controller != self.document.partner.view->lastController) {
    634             [self setRumble:0];
    635             self.document.partner.view->lastController = controller;
    636         }
    637     }
    638     else {
    639         if (controller != lastController) {
    640             [self setRumble:0];
    641             lastController = controller;
    642         }
    643     }
    644     return true;
    645 }
    646 
    647 - (void)controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes
    648 {
    649     if (!_gb) return;
    650     /* Always handle only the most dominant 2D input. */
    651     for (JOYAxes2D *otherAxes in controller.axes2D) {
    652         if (otherAxes == axes) continue;
    653         if (otherAxes.distance > axes.distance) {
    654             return;
    655         }
    656     }
    657     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    658 
    659     if ([self shouldControllerUseJoystickForMotion:controller] && !self.mouseControlsActive) {
    660         GB_set_accelerometer_values(_gb, -axes.value.x, -axes.value.y);
    661     }
    662     else if ([defaults boolForKey:@"GBFauxAnalogInputs"]) {
    663         unsigned playerCount = self.playerCount;
    664         for (unsigned player = 0; player < playerCount; player++) {
    665             unsigned effectivePlayer;
    666             GB_gameboy_t *effectiveGB;
    667             if (![self controller:controller applicableForPlayer:player effectivePlayer:&effectivePlayer effectiveGB:&effectiveGB]) continue;
    668             
    669             GB_set_use_faux_analog_inputs(effectiveGB, effectivePlayer, true);
    670             NSPoint position = axes.value;
    671             GB_set_faux_analog_inputs(effectiveGB, effectivePlayer, position.x, position.y);
    672         }
    673     }
    674 }
    675 
    676 - (void)controller:(JOYController *)controller movedAxes3D:(JOYAxes3D *)axes
    677 {
    678     if (!_gb) return;
    679     if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return;
    680     if (self.mouseControlsActive) return;
    681     if (controller != lastController) return;
    682     // When using Joy-Cons in dual-controller grip, ignore motion data from the left Joy-Con
    683     if (controller.joyconType == JOYJoyConTypeDual) {
    684         for (JOYController *child in [(JOYCombinedController *)controller children]) {
    685             if (child.joyconType != JOYJoyConTypeRight && [child.axes3D containsObject:axes]) {
    686                 return;
    687             }
    688         }
    689     }
    690     
    691     NSDictionary<NSNumber *, JOYController *> *controllerMapping = [self controllerMapping];
    692     GB_gameboy_t *effectiveGB = _gb;
    693     
    694     if (self.document.partner) {
    695         if (controllerMapping[@1] == controller) {
    696             effectiveGB = self.document.partner.gb;
    697         }
    698         if (controllerMapping[@0] != controller) {
    699             return;
    700         }
    701         
    702     }
    703 
    704     if (axes.usage == JOYAxes3DUsageOrientation) {
    705         for (JOYAxes3D *axes in controller.axes3D) {
    706             // Only use orientation if there's no acceleration axes
    707             if (axes.usage == JOYAxes3DUsageAcceleration) {
    708                 return;
    709             }
    710         }
    711         JOYPoint3D point = axes.normalizedValue;
    712         GB_set_accelerometer_values(effectiveGB, point.x, point.z);
    713     }
    714     else if (axes.usage == JOYAxes3DUsageAcceleration) {
    715         JOYPoint3D point = axes.gUnitsValue;
    716         GB_set_accelerometer_values(effectiveGB, point.x, point.z);
    717     }
    718 }
    719 
    720 - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
    721 {
    722     if (!_gb) return;
    723     if (![self allowController]) return;
    724     _mouseControlEnabled = false;
    725     if (button.type == JOYButtonTypeAxes2DEmulated && [self shouldControllerUseJoystickForMotion:controller]) return;
    726     
    727     [self tryAssigningController:controller];
    728     
    729     unsigned playerCount = self.playerCount;
    730 
    731     IOPMAssertionID assertionID;
    732     IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
    733     
    734     
    735     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    736     bool fauxAnalog = [defaults boolForKey:@"GBFauxAnalogInputs"];
    737 
    738     for (unsigned player = 0; player < playerCount; player++) {
    739         unsigned effectivePlayer;
    740         GB_gameboy_t *effectiveGB;
    741         if (![self controller:controller applicableForPlayer:player effectivePlayer:&effectivePlayer effectiveGB:&effectiveGB]) continue;
    742         
    743         NSDictionary *mapping = [defaults dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
    744         if (!mapping) {
    745             mapping = [defaults dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
    746         }
    747         
    748         JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: button.usage;
    749         if (!mapping && usage >= JOYButtonUsageGeneric0) {
    750             usage = GB_inline_const(JOYButtonUsage[], {JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX})[(usage - JOYButtonUsageGeneric0) & 3];
    751         }
    752         
    753         if (usage >= JOYButtonUsageDPadLeft && usage <= JOYButtonUsageDPadDown) {
    754             if (fauxAnalog && button.type == JOYButtonTypeAxes2DEmulated) {
    755                 // This isn't a real button, it's an emulated Axes2D. We want to handle it as an Axes2D instead
    756                 continue;
    757             }
    758             else {
    759                 // User used a digital direction input, revert to non-analog inputs
    760                 GB_set_use_faux_analog_inputs(effectiveGB, effectivePlayer, false);
    761             }
    762         }
    763         
    764         switch ((unsigned)usage) {
    765                 
    766             case JOYButtonUsageNone: break;
    767             case JOYButtonUsageA: GB_set_key_state_for_player(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break;
    768             case JOYButtonUsageB: GB_set_key_state_for_player(effectiveGB, GB_KEY_B, effectivePlayer, button.isPressed); break;
    769             case JOYButtonUsageC: break;
    770             case JOYButtonUsageStart:
    771             case JOYButtonUsageX: GB_set_key_state_for_player(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break;
    772             case JOYButtonUsageSelect:
    773             case JOYButtonUsageY: GB_set_key_state_for_player(effectiveGB, GB_KEY_SELECT, effectivePlayer, button.isPressed); break;
    774             case JOYButtonUsageR2:
    775             case JOYButtonUsageL2:
    776             case JOYButtonUsageZ: {
    777                 self.isRewinding = button.isPressed;
    778                 if (button.isPressed) {
    779                     if (self.document.isSlave) {
    780                         GB_set_turbo_mode(self.document.partner.gb, false, false);
    781                     }
    782                     else {
    783                         GB_set_turbo_mode(_gb, false, false);
    784                     }
    785                     _turbo = false;
    786                 }
    787                 break;
    788             }
    789         
    790             case JOYButtonUsageL1: {
    791                 if (!analogClockMultiplierValid || analogClockMultiplier == 1.0 || !button.isPressed) {
    792                     if (self.document.isSlave) {
    793                         GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false);
    794                     }
    795                     else {
    796                         GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding);
    797                     }
    798                     _turbo = button.isPressed;
    799                 }
    800                 break;
    801             }
    802 
    803             case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break;
    804             case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(effectiveGB, GB_KEY_LEFT, effectivePlayer, button.isPressed); break;
    805             case JOYButtonUsageDPadRight: GB_set_key_state_for_player(effectiveGB, GB_KEY_RIGHT, effectivePlayer, button.isPressed); break;
    806             case JOYButtonUsageDPadUp: GB_set_key_state_for_player(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break;
    807             case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break;
    808 
    809             case GBJoyKitRapidA:
    810                 _rapidA[effectivePlayer] = button.isPressed;
    811                 _rapidACount[effectivePlayer] = 0;
    812                 GB_set_key_state_for_player(_gb, GB_KEY_A, effectivePlayer, button.isPressed);
    813                 break;
    814                 
    815             case GBJoyKitRapidB:
    816                 _rapidB[effectivePlayer] = button.isPressed;
    817                 _rapidBCount[effectivePlayer] = 0;
    818                 GB_set_key_state_for_player(_gb, GB_KEY_B, effectivePlayer, button.isPressed);
    819                 break;
    820         }
    821     }
    822 }
    823 
    824 - (BOOL)acceptsFirstResponder
    825 {
    826     return true;
    827 }
    828 
    829 - (bool)mouseControlsActive
    830 {
    831     return _gb && GB_is_inited(_gb) && GB_has_accelerometer(_gb) &&
    832            _mouseControlEnabled && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7AllowMouse"];
    833 }
    834 
    835 - (void)mouseEntered:(NSEvent *)theEvent
    836 {
    837     if (!mouse_hidden) {
    838         mouse_hidden = true;
    839         if (_mouseHidingEnabled &&
    840             !self.mouseControlsActive) {
    841             [NSCursor hide];
    842         }
    843     }
    844     [super mouseEntered:theEvent];
    845 }
    846 
    847 - (void)mouseExited:(NSEvent *)theEvent
    848 {
    849     if (mouse_hidden) {
    850         mouse_hidden = false;
    851         if (_mouseHidingEnabled) {
    852             [NSCursor unhide];
    853         }
    854     }
    855     [super mouseExited:theEvent];
    856 }
    857 
    858 - (void)mouseDown:(NSEvent *)event
    859 {
    860     _mouseControlEnabled = true;
    861     if (self.mouseControlsActive) {
    862         if (event.type == NSEventTypeLeftMouseDown) {
    863             GB_set_key_state(_gb, GB_KEY_A, true);
    864         }
    865     }
    866 }
    867 
    868 - (void)mouseUp:(NSEvent *)event
    869 {
    870     if (self.mouseControlsActive) {
    871         if (event.type == NSEventTypeLeftMouseUp) {
    872             GB_set_key_state(_gb, GB_KEY_A, false);
    873         }
    874     }
    875 }
    876 
    877 - (void)mouseMoved:(NSEvent *)event
    878 {
    879     if (self.mouseControlsActive) {
    880         NSPoint point = [self convertPoint:[event locationInWindow] toView:nil];
    881         
    882         point.x /= self.frame.size.width;
    883         point.x *= 2;
    884         point.x -= 1;
    885         
    886         point.y /= self.frame.size.height;
    887         point.y *= 2;
    888         point.y -= 1;
    889         
    890         if (GB_get_screen_width(_gb) != 160) { // has border
    891             point.x *= 256 / 160.0;
    892             point.y *= 224 / 114.0;
    893         }
    894         GB_set_accelerometer_values(_gb, -point.x, point.y);
    895     }
    896 }
    897 
    898 - (void)setMouseHidingEnabled:(bool)mouseHidingEnabled
    899 {
    900     if (mouseHidingEnabled == _mouseHidingEnabled) return;
    901     if (![NSThread isMainThread]) {
    902         dispatch_async(dispatch_get_main_queue(), ^{
    903             [self setMouseHidingEnabled:mouseHidingEnabled];
    904         });
    905         return;
    906     }
    907 
    908     _mouseHidingEnabled = mouseHidingEnabled;
    909     
    910     if (mouse_hidden && _mouseHidingEnabled) {
    911         [NSCursor hide];
    912     }
    913 
    914     if (mouse_hidden && !_mouseHidingEnabled) {
    915         [NSCursor unhide];
    916     }
    917 }
    918 
    919 - (bool)isMouseHidingEnabled
    920 {
    921     return _mouseHidingEnabled;
    922 }
    923 
    924 - (void) flagsChanged:(NSEvent *)event
    925 {
    926     if (event.modifierFlags > previousModifiers) {
    927         [self keyDown:event];
    928     }
    929     else {
    930         [self keyUp:event];
    931     }
    932     
    933     previousModifiers = event.modifierFlags;
    934 }
    935 
    936 -(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
    937 {
    938     NSPasteboard *pboard = [sender draggingPasteboard];
    939     
    940     if ( [[pboard types] containsObject:NSURLPboardType] ) {
    941         NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
    942         if (GB_is_save_state(fileURL.fileSystemRepresentation)) {
    943             return NSDragOperationGeneric;
    944         }
    945     }
    946     return NSDragOperationNone;
    947 }
    948 
    949 -(BOOL)performDragOperation:(id<NSDraggingInfo>)sender
    950 {
    951     NSPasteboard *pboard = [sender draggingPasteboard];
    952     
    953     if ( [[pboard types] containsObject:NSURLPboardType] ) {
    954         NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
    955         return [_document loadStateFile:fileURL.fileSystemRepresentation noErrorOnNotFound:false];
    956     }
    957 
    958     return false;
    959 }
    960 
    961 - (NSImage *)renderToImage;
    962 {
    963     /* Not going to support this on OpenGL, OpenGL is too much of a terrible API for me
    964        to bother figuring out how the hell something so trivial can be done. */
    965     return nil;
    966 }
    967 @end

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.