Prevent error “funk” sound in event monitor OS X

2020-07-13 11:00发布

问题:

I'm writing an app in swift that lives in the menu bar at the top of the screen. I need both a global and local event monitor to open the popover on a specific key press. There is no problem with the local event monitor, but when the user hits the key command (cmd+shift+8) from inside an app like Finder, the popover opens but the mac error "Funk" sound is played as well. Is there any way I can disable this? Perhaps some way for the app to eat the sound, or register it as a valid keyboard shortcut so the sound is never played?

Here is the code:

        NSEvent.addGlobalMonitorForEvents(matching: NSEventMask.keyDown, handler: {(event: NSEvent!) -> Void in
        if (event.keyCode == 28 && event.modifierFlags.contains(NSEventModifierFlags.command) && event.modifierFlags.contains(NSEventModifierFlags.shift)){
            self.togglePopover(sender: self)
        }
    });

    NSEvent.addLocalMonitorForEvents(matching: NSEventMask.keyDown, handler: {(event: NSEvent!) -> NSEvent? in
        if (event.keyCode == 28 && event.modifierFlags.contains(NSEventModifierFlags.command) && event.modifierFlags.contains(NSEventModifierFlags.shift)){
            self.togglePopover(sender: self)
        }
        return event
    });

回答1:

I ended up using MASShortcut as a workaround solution to this issue.



回答2:

  1. In your addGlobalMonitorForEventsMatchingMask handler, save the current volume level and turn the volume down on both channels. You can do you own event processing in the handler before or after turning down the volume.

  2. Before returning from the handler, restore the original volume, but include a delay to give the OS time to process the event (it will send the "funk," but you won't hear it).

One side effect: if you're listening to something (e.g., music), that will be briefly silenced, too.

My event code:

[NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown
                                       handler:^(NSEvent *event) {
    if ( event.type == NSEventTypeKeyDown ) {
        if ( event.keyCode == 106 ) { // F16
            // process the event here
            [self adjustVolume:@(NO)];
            [self performSelector:@selector(adjustVolume:) withObject:@(YES) afterDelay:0.3];
            // 0.2 seconds was too soon
        }
    }
}];

My volume adjustment code:

- (void)adjustVolume:(NSNumber *)offOn
{
    // get audio device...
    AudioObjectPropertyAddress getDefaultOutputDevicePropertyAddress = {
        kAudioHardwarePropertyDefaultOutputDevice,
        kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMaster
    };

    AudioDeviceID defaultOutputDeviceID;
    UInt32 infoSize = sizeof(defaultOutputDeviceID);
    AudioObjectGetPropertyData(kAudioObjectSystemObject,
                               &getDefaultOutputDevicePropertyAddress,
                               0, NULL,
                               &infoSize, &defaultOutputDeviceID);

    // structurs to access the left/right volume setting
    AudioObjectPropertyAddress volumePropertyAddress1 = {
        kAudioDevicePropertyVolumeScalar,
        kAudioDevicePropertyScopeOutput,
        1 /* left */
    };
    AudioObjectPropertyAddress volumePropertyAddress2 = {
        kAudioDevicePropertyVolumeScalar,
        kAudioDevicePropertyScopeOutput,
        2 /* right */
    };

    // save the original volume (assumes left/right are the same
    static Float32 volumeOriginal; // could be an iVar

    if ( offOn.boolValue == NO ) { // turn off 

        UInt32 volumedataSize = sizeof(volumeOriginal);
        AudioObjectGetPropertyData(defaultOutputDeviceID,
                                   &volumePropertyAddress1,
                                   0, NULL,
                                   &volumedataSize, &volumeOriginal);
        //NSLog(@"volumeOriginal %f",volumeOriginal);

        // turn off both channels
        Float32 volume = 0.0;
        AudioObjectSetPropertyData(defaultOutputDeviceID,
                                   &volumePropertyAddress1,
                                   0, NULL,
                                   sizeof(volume), &volume);
        AudioObjectSetPropertyData(defaultOutputDeviceID,
                                   &volumePropertyAddress2,
                                   0, NULL,
                                   sizeof(volume), &volume);

    } else { // restore

        //NSLog(@"restoring volume");
        AudioObjectSetPropertyData(defaultOutputDeviceID,
                                   &volumePropertyAddress1,
                                   0, NULL,
                                   sizeof(volumeOriginal), &volumeOriginal);
        AudioObjectSetPropertyData(defaultOutputDeviceID,
                                   &volumePropertyAddress2,
                                   0, NULL,
                                   sizeof(volumeOriginal), &volumeOriginal);
    }
}

With recognition to Thomas O'Dell for getting me started on this Change OS X system volume programmatically