能否通过终端或AppleScript编程方式来自动更改OSX键盘布局(输入源)?

18

我目前通过在Alfred上运行GUI AppleScript来切换输入源,GUI脚本有时需要长达1秒才能完成更改。有时会变得非常烦人。

我发现了Determine OS X keyboard layout (“input source”) in the terminal/a script。我想知道是否有一种程序化的方法来更改输入源,既然我们可以找出当前输入源?我已尝试覆盖com.apple.HIToolbox.plist,但它并没有更改输入。

(我确实意识到在系统首选项中有将快捷键映射到输入源的功能,但我更喜欢将关键字与Alfred映射)

6个回答

18

您可以使用文本输入服务 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
+1 鼓励你承担繁重的 Carbon 工作。(难道不应该是轻量级的 Carbon 吗?)开启 ARC 后,我建议你将第一个 __bridge 替换为 __bridge_transfer,以避免由于未释放 TISCreateInputSourceList() 分配的 CFArrayRef 导致泄漏。 - mklement0
@mklement0,你说得没错,确实存在泄漏问题。谢谢。我使用了CFBridgingRelease()来解决这个问题,我更喜欢它而不是__bridge_transfer - Ken Thomases
2
构建此项目的步骤(Xcode 6):新建项目 > 命令行工具 > 输入细节,选择 Objective-C 作为 语言。在 main.m 的顶部添加 @import carbon;。将代码粘贴到 main() 函数内的适当位置。 - mklement0
这在我的系统上只是退出255,没有错误消息。(构建为https://dev59.com/O2Ag5IYBdhLWcg3wU5m8#63232278) - HappyFace

8
@Ken Thomases的解决方案可能是最健壮的 - 但它需要创建一个命令行实用程序。
不幸的是,非GUI脚本外壳脚本/ AppleScripting解决方案不可行:虽然可以更新反映当前选择的输入源(键盘布局)的*.plist文件 - 〜/ Library / Preferences / com.apple.HIToolbox.plist - 系统将忽略更改。
然而,以下GUI脚本解决方案(基于此内容),虽然仍涉及可见操作,但在我的计算机上健壮且相当快速(约为0.2秒):
(如果您只想循环通过安装的布局,使用在系统首选项中定义的键盘快捷键可能是最佳选择;解决方案的优点是您可以针对特定的布局。)
请注意评论中提到的先决条件。
# 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的程序都是应用程序。 - Ken Thomases
1
谢谢大家,基于@KenThomases的解决方案,我编写了一个简单的CLI,可以通过Alfred运行。效果非常好。 - maxhungry
@maxhungry,你用了什么工具来编写CLI?我似乎无法弄清楚这个问题。虽然我也很想拥有这个实用程序。 - ruslaniv
@Rusl:我在Ken Thomases的回答中添加了步骤。 - mklement0
1
@mklement0,在尝试了 Mojave 后,我发现你不需要在开头使用 select,并且你可以使其“隐形”,但是在“end tell”到“menu 1”之后,输入源的更改不会完成,除非最后点击一下。 (或者,您可以在“tell menu 1”之前放置一个单击,这确实会使选择可见。)谢谢! - zen
显示剩余5条评论

5

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

对于那些想要构建@Ken Thomases' solution但不想安装Xcode(它占用几GB,如果没有严肃使用就完全没有必要花费这么多空间),可以使用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程序,因此这可能不是最佳方案,但我希望能够创建一个可接受键盘布局作为命令行参数的可执行文件。对于有兴趣的人,以下是我想出来的解决方案:

在第二步中使用以下代码:

#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 注:它只允许切换到已在您的系统上配置的布局!

1
谢谢,帮了大忙。 此外,这是一个有用的 GitHub 仓库,可以获取输入语言 ID 列表:https://github.com/minoki/InputSourceSelector - Alex Barkun
10x 如果你选择了正确的语言ID,它会很好用。"ABC" 对于英语来说是可以的。 - undefined

4

另一个选择是使用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的回答。


这个脚本在我的系统上需要大约0.5秒来切换键盘。 - Quantum7
@Quantum7 在我的 Mac 上几乎是瞬间完成的。尝试使用 swiftc script_file_name 编译可执行文件。也许运行该文件会更快。 - pointum

1

在AppleScript中,您只需要按下cmd +“空格”(或其他用于更改键盘源的内容)。

而您所需的一切:

    key code 49 using command down

49是AppleScript中代表“空格”按键的ASCII码。

附注:不要忘记在系统偏好设置中获取您的AppleScript工具的访问权限。


我已经使用这个解决方案一段时间了,但有时会出现问题,因为如果我同时在键盘上打字并且脚本中的键也在同一时间生效,就会触发某些情况下不想要的组合。 - Bence Szalai
请删除与键盘扫描码完全无关的“ASCII”混淆引用。 - Devon

0
tell application "System Events"
    key code 49 using control down
end tell

通过按键更改布局


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接