Search code examples
macoscocoakeyboardmacos-carbon

OSX Cocoa input source detect change


Does anyone know how to detect when the user changes the current input source in OSX?

Switching my keyboard settings to German

I can call TISCopyCurrentKeyboardInputSource() to find out which input source ID is being used like this:

    TISInputSourceRef isource = TISCopyCurrentKeyboardInputSource();
    if ( isource == NULL )
    {
        cerr << "Couldn't get the current input source\n.";
        return -1;
    }

    CFStringRef id = (CFStringRef)TISGetInputSourceProperty(
        isource, 
        kTISPropertyInputSourceID);
    CFRelease(isource);

If my input source is "German", then id ends up being "com.apple.keylayout.German", which is mostly what I want. Except:

  1. The results of TISCopyCurrentKeyboardInputSource() doesn't change once my process starts? In particular, I can call TISCopyCurrentKeyboardInputSource() in a loop and switch my input source, but TISCopyCurrentKeyboardInputSource() keeps returning the input source that my process started with.
  2. I'd really like to be notified when the input source changes. Is there any way of doing this? To get a notification or an event of some kind telling me that the input source has been changed?

Solution

  • You can observe the NSTextInputContextKeyboardSelectionDidChangeNotification notification posted by NSTextInputContext to the default Cocoa notification center. Alternatively, you can observe the kTISNotifySelectedKeyboardInputSourceChanged notification delivered via the Core Foundation distributed notification center.

    However, any such change starts in a system process external to your app. The system then notifies the frameworks in each app process. The frameworks can only receive such notifications when it is allowed to run its event loop. Likewise, if you're observing the distributed notification yourself, that can only happen when the event loop (or at least the main thread's run loop) is allowed to run.

    So, that explains why running a loop which repeatedly checks the result of TISCopyCurrentKeyboardInputSource() doesn't work. You're not allowing the frameworks to monitor the channel over which it would be informed of the change. If, rather than a loop, you were to use a repeating timer with a low enough frequency that other stuff has a chance to run, and you returned control to the app's event loop, you would see the result of TISCopyCurrentKeyboardInputSource() changing.