Is it possible to disable "funk" error sound on global keyboard events in SwiftUI (macOS)?

418 Views Asked by At

I've started a SwiftUI project (it is a macOS tray application) that relies on global keyboard events (even when my application is minimized). Specifically i care about the F3 and F4 keys. While the keyboard events are registered correctly and my application is fully functional it is always playing that error "funk" sound when a key is pressed. Does anyone know how to fix this?

MyApp.swift

import SwiftUI

@main
struct MyApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate;
    var body: some Scene {
        Settings {
            ContentView()
        }
    }
}

class AppDelegate: NSObject,NSApplicationDelegate {
    var statusItem: NSStatusItem!
    var popOver: NSPopover!
    
    func applicationDidFinishLaunching(_ notification: Notification){
        let contentView = ContentView()
        let popOver = NSPopover();
        popOver.behavior = .transient
        popOver.animates = true
        popOver.contentViewController = NSHostingController(rootView: contentView)
        popOver.setValue(true, forKeyPath: "shouldHideAnchor")
        
        self.popOver = popOver
        self.statusItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
        
        let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String : true]
        let accessEnabled = AXIsProcessTrustedWithOptions(options)
        
        if !accessEnabled {
            print("Access Not Enabled")
        }

        // Here is where the global keypress event is registered
        NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { (event) in
            if (event.keyCode == 99) {
                // do smth
            }else if (event.keyCode == 118) {
                // do smth else
                }
            }
        }
        
        if let MenuButton = self.statusItem.button {
            MenuButton.image = NSImage(systemSymbolName: "display.2", accessibilityDescription: nil)
            MenuButton.action = #selector(MenuButtonToggle)
        }
    }
    
    @objc func MenuButtonToggle(_ sender: AnyObject){
        if let button = self.statusItem.button {
            if self.popOver.isShown{
                self.popOver.performClose(sender)
            }else {
                self.popOver.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
                self.popOver.contentViewController?.view.window?.makeKey()
            }
        }
    }
}
2

There are 2 best solutions below

4
DaWiseguy On

It looks like you can achieve this by assigning the keys through the view directly at least, perhaps this can work on your AppDelegate also but you need to override.

Here is a working example:

struct KeyEventHandling: NSViewRepresentable {
    
    class KeyView: NSView {
            func isManagedByThisView(_ event: NSEvent) -> Bool {
                //...
                return true
            }
            
            override var acceptsFirstResponder: Bool { true }
            override func keyDown(with event: NSEvent) {
                if isManagedByThisView(event) {
                    print(">> key \(event.keyCode)")
                } else {
                    super.keyDown(with: event)
                }
            }
    }
    
    func makeNSView(context: Context) -> NSView {
        let view = KeyView()
        DispatchQueue.main.async { // wait till next event cycle
            view.window?.makeFirstResponder(view)
        }
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {
    }
    
}

struct ContentView: View {
    var body: some View {
        KeyEventHandling()
    }
}

According to Documentation: "When you call super.keyDown(with: event), the event goes up through the responder chain and if no other responders process it, causes beep sound." Good Luck!

0
Andrew_STOP_RU_WAR_IN_UA On
struct DisableBeepsView: NSViewRepresentable {
    class KeyView: NSView {
            func isManagedByThisView(_ event: NSEvent) -> Bool {
                return true
            }
            
            override var acceptsFirstResponder: Bool { true }
            override func keyDown(with event: NSEvent) {
                if isManagedByThisView(event) {
//                    print(">> key \(event.keyCode)")
                } else {
                    super.keyDown(with: event)
                }
            }
    }
    
    func makeNSView(context: Context) -> NSView {
        let view = KeyView()
        DispatchQueue.main.async { // wait till next event cycle
            view.window?.makeFirstResponder(view)
        }
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {}
}

just locate DisableBeepsView() inside your View where you need to disable beep sound