【问题标题】:Change OSX keyboard layout("input source") programmatically via terminal or AppleScript?通过终端或 AppleScript 以编程方式更改 OSX 键盘布局(“输入源”)?
【发布时间】:2014-07-06 22:31:38
【问题描述】:

我目前正在通过 Alfred 运行 GUI AppleScript 来切换输入源,而 GUI 脚本有时可能需要 1 秒才能完成更改。有时会很烦人。

我遇到了Determine OS X keyboard layout (“input source”) in the terminal/a script。我想知道,如果有办法以编程方式更改输入源,我们可以找出当前的输入源吗?我试过覆盖 com.apple.HIToolbox.plist 但它不会改变输入。

(我确实意识到系统偏好设置中有输入源的映射快捷方式,但我更喜欢使用 Alfred 映射关键字)

【问题讨论】:

    标签: macos applescript alfred


    【解决方案1】:

    您可以使用文本输入服务 API 来做到这一点:

    NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)@{ (__bridge NSString*)kTISPropertyInputSourceID : @"com.apple.keylayout.French" }, FALSE));
    TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
    OSStatus status = TISSelectInputSource(source);
    if (status != noErr)
        /* handle error */;
    

    第一行中的字典可以将其他属性用于选择输入源的其他条件。

    还有NSTextInputContext。它有一个selectedKeyboardInputSource,可以设置为输入源 ID 以选择不同的输入源。那里的问题是您需要一个 NSTextInputContext 的实例才能使用,并且其中一个实例仅在您有一个带有文本视图的关键窗口作为其第一响应者时才存在。

    【讨论】:

    • +1 用于举重碳。 (碳不应该是轻的吗?)。启用 ARC 后,我建议您将第一个 __bridge 替换为 __bridge_transfer,以避免因未释放 TISCreateInputSourceList() 分配的 CFArrayRef 而导致泄漏。
    • @mklement0,你说得对。谢谢。通过使用CFBridgingRelease() 修复,我更喜欢__bridge_transfer
    • 从 Xcode 6 开始构建这个的步骤:New Project > Command Line Tool > 输入详细信息,在Language 下选择Objective-C。在main.m 的顶部,添加@import carbon;。将代码粘贴到main() 函数内的适当位置。
    • 这只是在我的系统上退出 255,没有错误消息。 (构建为stackoverflow.com/a/63232278/1410221
    【解决方案2】:

    @Ken Thomases' solution 可能是最强大的 - 但它需要创建一个命令行实用程序。

    不幸的是,非 GUI 脚本 shell 脚本/AppleScripting 解决方案不是一个选项:虽然它可以更新@987654323 @ 反映当前选择的输入源(键盘布局)的文件 - ~/Library/Preferences/com.apple.HIToolbox.plist - 系统将忽略更改。

    但是,以下 GUI 脚本解决方案(基于 this)虽然仍涉及可见操作,但稳健且相当快速 在我的机器上(大约 0.2 秒):

    (如果您只是想循环通过已安装的布局,使用系统偏好设置中定义的键盘快捷键可能是您最好的选择;解决方案的优点是您可以针对特定的布局。)

    注意 cmets 中提到的先决条件。

    # Example call
    my switchToInputSource("Spanish")
    
    # Switches to the specified input source (keyboard layout) using GUI scripting.
    # Prerequisites:
    #   - The application running this script must be granted assisistive access.
    #   - Showing the Input menu in the menu bar must be turned on 
    # (System Preferences > Keyboard > Input Sources > Show Input menu in menu bar).
    # Parameters:
    #    name ... input source name, as displayed when you open the Input menu from
    #             the menu bar; e.g.: "U.S."
    # Example:
    #   my switchToInputSource("Spanish")
    on switchToInputSource(name)
        tell application "System Events" to tell process "SystemUIServer"
            tell (menu bar item 1 of menu bar 1 whose description is "text input")
                # !! Sadly, we must *visibly* select (open) the text-input menu-bar extra in order to
                # !! populate its menu with the available input sources.
                select
                tell menu 1
                    # !! Curiously, using just `name` instead of `(get name)` didn't work: 'Access not allowed'.
                    click (first menu item whose title = (get name))
                end tell
            end tell
        end tell
    end switchToInputSource
    

    【讨论】:

    • 我的回答不需要创建 Cocoa 应用程序。它可以构建为命令行工具。
    • @KenThomases:知道了 - 已更正。如果您添加更多关于如何为您的答案创建一个的指导,这将有所帮助。
    • @KenThomases:另外,您可能对“Cocoa 应用程序”的含义有误解:您的代码使用NSArray,它 Cocoa类(来自 Cocoa 的 Foundation 框架);但是,您确实并不严格需要 Cocoa,因为您引用的 API 是 Carbon/HIToolbox API(另一方面,NSTextInputContext,是一个 Cocoa API)。您是否将 Cocoa 与 AppKit(Cocoa 中与 UI 相关的部分)混淆了,还是我遗漏了什么?
    • 谢谢大家,基于@KenThomases 的解决方案,我编写了一个由 Alfred 运行的简单 CLI。像奇迹一样工作。
    • @mklement0 所以,在 Mojave 上试过之后,我发现你不需要一开始的选择,你可以让它“不可见”,但是输入源的改变不会'在“end tell”到“menu 1”之后没有最后的“click”就完成了。 (或者,您可以在“告诉菜单 1”之前单击,这确实会使选择可见。)谢谢!
    【解决方案3】:

    在 AppleScript 上,您只能使用 cmd + "space"(或其他东西,用于更改键盘源)。

    还有你需要的一切:

        key code 49 using command down
    

    49 - AppleScript ASCII 中的“空格”按钮代码。

    P.S.:别忘了在系统偏好设置中获取 AppleScript 实用程序的访问权限。

    【讨论】:

    • 我使用这个解决方案已经有一段时间了,但有时它会引起问题,因为如果我同时输入键盘和脚本中的键会同时触发在某些情况下不需要的组合。
    • 请删除对“ASCII”的混淆引用,它与键盘扫描码完全无关。
    【解决方案4】:
    tell application "System Events"
        key code 49 using control down
    end tell
    

    通过按键更改布局

    【讨论】:

      【解决方案5】:

      使用 Xcode 命令行工具的解决方案

      对于那些想要构建 @Ken Thomases' solution 但没有安装 Xcode(这是几个 GiB 并且除非认真使用,否则花费这么多空间完全没用)的人,可以使用 Xcode 命令行工具来构建它。

      互联网上有几个关于如何安装 Xcode 命令行工具的教程。这里的要点是,与成熟的 Xcode 相比,它只占用了一小部分空间。

      安装后,步骤如下:

      1. 创建一个名为whatever.m的文件
      2. 在whatever.m 中输入以下内容:
      #include <Carbon/Carbon.h>
      
      int main (int argc, const char * argv[]) {
          NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)@{ (__bridge NSString*)kTISPropertyInputSourceID : @"com.apple.keylayout.French" }, FALSE));
          TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
          OSStatus status = TISSelectInputSource(source);
          if (status != noErr)
              return -1;
      
          return 0;
      }
      
      1. French 替换为您想要的布局。
      2. 保存文件
      3. 在whatever.m所在的文件夹中打开终端
      4. 运行此命令: clang -framework Carbon whatever.m -o whatever

      您的应用程序在同一文件夹中创建为whatever,可以按以下方式执行: .\whatever

      另外

      我从未创建过任何 Objective-C 程序,所以这可能不是最理想的,但我想要一个可以将键盘布局作为命令行参数的可执行文件。对于任何有兴趣的人,这是我想出的解决方案:

      在第 2 步中使用此代码:

      #import <Foundation/Foundation.h>
      #include <Carbon/Carbon.h>
      
      int main (int argc, const char * argv[]) {
          NSArray *arguments = [[NSProcessInfo processInfo] arguments];
      
          NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)@{ (__bridge NSString*)kTISPropertyInputSourceID : [@"com.apple.keylayout." stringByAppendingString:arguments[1]] }, FALSE));
          TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
          OSStatus status = TISSelectInputSource(source);
          if (status != noErr)
              return -1;
      
          return 0;
      }
      

      在第 6 步中,运行以下命令: clang -framework Carbon -framework Foundation whatever.m -o whatever

      您现在可以从命令行切换到任何布局,例如: ./whatever British

      注意:它只允许切换到系统上已配置的布局!

      【讨论】:

        【解决方案6】:

        另一个选择是使用 Swift。它可以以类似脚本的方式使用(无需编译)。

        • 安装 Xcode 命令行工具
        • 从下面的代码创建一个脚本
        • 使用swift script_file_name 运行脚本

        代码:

        import Carbon
        
        let command = ProcessInfo.processInfo.arguments.dropFirst().last ?? ""
        let filter = command == "list" ? nil : [kTISPropertyInputSourceID: command]
        
        guard let cfSources = TISCreateInputSourceList(filter as CFDictionary?, false),
              let sources = cfSources.takeRetainedValue() as? [TISInputSource] else {
            print("Use \"list\" as an argument to list all enabled input sources.")
            exit(-1)
        }
        
        if filter == nil { // Print all sources
            print("Change input source by passing one of these names as an argument:")
            sources.forEach {
                let cfID = TISGetInputSourceProperty($0, kTISPropertyInputSourceID)!
                print(Unmanaged<CFString>.fromOpaque(cfID).takeUnretainedValue() as String)
            }
        } else if let firstSource = sources.first { // Select this source
            exit(TISSelectInputSource(firstSource))
        }
        

        本文详细阐述了Ken Thomasessbnc.eu 的回答。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-11-28
          • 2018-12-15
          相关资源
          最近更新 更多