Search code examples
macosdaemonkeyboard-layout

OS X. How to get TISInputSourceRef (keyboard layout) of current active window of all system in daemon?


I want to get keyboard layout something like GetKeyboardLayout(threadId) in Windows. threadId is id of application with active window. I want to translate virtual key code to char of current language.

Now I use TISCopyCurrentKeyboardInputSource() function but it don't have parameters and it return only "U. S." language. I think this is layout of daemon. Also I can't convert code with TISInputSourceRef of TISCopyInputSourceForLanguage(language) function. It return only US characters.

UPDATE: Okey. I find solution of converting keyCodes. I jast call function UCKeyTranslate with parameter modifiers equal 0. But I cant't find how to get input source of active window or just any running application.

UPDATE 2: plist path is /Library/LaunchAgents

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>my.keylogger</string>
    <key>Program</key>
    <string>/Users/Titan/Desktop/keylogger</string>
    <key>ProgramArguments</key>
    <array>
      <string>keylogger</string>
    </array>
    <key>KeepAlive</key>
    <true/>
  </dict>
</plist>

Solution

  • Whenever you want to access the state of user sessions from a daemon, You're Doing It Wrong™. It can't be made to work reliably.

    The standard solution is to use user agents that get launched in each user session. Each agent can contact a central daemon and report about the state of the user session, if necessary.

    See Technical Note TN2083: Daemons and Agents for more details.


    Update:

    The above technote is relevant for understanding why you can't do this from a daemon.

    For the agent, it should use the distributed notification center to observe the kTISNotifySelectedKeyboardInputSourceChanged notification to learn when the selected keyboard input source changes. Then it can call TISCopyCurrentKeyboardInputSource() to learn what keyboard input source is current.

    In order for the agent to receive the notification, it must run the run loop of its main thread in one of the common modes. In a normal Cocoa app, this is done for you as part of the main event loop. In a different kind of program, you have to do this yourself.

    BOOL shouldKeepRunning = YES;        // global
    ...
    
    [[NSDistributedNotificationCenter defaultCenter] addObserver:self
                                                        selector:@selector(myMethod:)
                                                            name:(__bridge NSString*)kTISNotifySelectedKeyboardInputSourceChanged
                                                          object:nil
                                              suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
    
    while (shouldKeepRunning && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
        /* do nothing */;
    
    ...
    - (void) myMethod:(NSNotification*)note
    {
        TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
        // do something with inputSource ...
    }