2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

macOSでアプリごとに入力ソース(日本語/英数)を自動で切り替える仕組みを作った話

2
Posted at

はじめに

エディタでは「英数」、Slackやブラウザでは「日本語」——アプリを切り替えるたびに かな/英数 キーを押し直すの、地味にストレスじゃないですか?

私は1日に何百回もこの切り替えをやっていることに気づき、「アプリごとに入力ソースを自動で固定できないか」と思って個人開発で仕組みを作りました。この記事ではその実装の考え方を共有します。

やりたいこと

  • VS Code・ターミナルにフォーカス → 自動で 英数
  • Slack・メモ・チャット → 自動で 日本語
  • ブラウザは開いているサイトごとに切り替えたい(GitHubは英数、ニュースサイトは日本語…)

実装の方針

1. アプリの切り替えを検知する

フォアグラウンドアプリが変わったことは NSWorkspace の通知で取れます。

NSWorkspace.shared.notificationCenter.addObserver(
    self,
    selector: #selector(activeAppChanged(_:)),
    name: NSWorkspace.didActivateApplicationNotification,
    object: nil
)

@objc func activeAppChanged(_ note: Notification) {
    guard let app = note.userInfo?[NSWorkspace.applicationUserInfoKey]
            as? NSRunningApplication,
          let bundleID = app.bundleIdentifier else { return }
    // bundleID ごとに、設定された入力ソースへ切り替える
    switchInputSource(for: bundleID)
}

2. 入力ソースを切り替える

入力ソースの取得・選択は Carbon の TextInputSources API(TISInputSource)を使います。

import Carbon

func selectInputSource(id: String) {
    guard let list = TISCreateInputSourceList(nil, false)?
            .takeRetainedValue() as? [TISInputSource] else { return }
    for source in list {
        guard let ptr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID)
        else { continue }
        let sourceID = Unmanaged<CFString>.fromOpaque(ptr).takeUnretainedValue() as String
        if sourceID == id {
            TISSelectInputSource(source)
            return
        }
    }
}

bundleID → 入力ソースID のマッピングを持っておけば、アプリを切り替えるだけで勝手に入力ソースが変わります。

3. ブラウザはURL単位で切り替える

ブラウザは「アプリ」では区別できないので、アクティブタブのURLが必要です。これは Accessibility(AX)API でフロントのウィンドウからアドレスバーの値を読み取る方針になります(要・アクセシビリティ権限)。ホスト名でルールを引いて切り替えます。

ハマりどころ

  • 切り替え直後に通知が連続で飛ぶことがあるので、短時間のデバウンスを入れないと入力ソースがチラつきます。
  • ユーザーが手動で切り替えたときは尊重したい、など「自動」と「手動」の競合をどう扱うかが地味に難しいポイントでした。

ついでに:第三者製マウスのスクロールも

入力まわりを触っているついでに、サードパーティ製マウスの「カクカクするスクロール」を、CGEventTap で慣性付きのスムーズスクロールに変換する処理も入れました。サイドボタンのリマップもアプリごとに設定できます。

個人開発のアプリとして公開しています

この仕組みを LinguaX というmacOSアプリにまとめて公開しています。入力ソースの自動切り替え + 第三者製マウスの強化を、メニューバー常駐の軽量アプリ(無料プランあり)として動かしています。

実装の詳細や「こういうケースはどうしてる?」など、コメントで気軽に聞いてください。

まとめ

  • アプリ切り替えの検知は NSWorkspace.didActivateApplicationNotification
  • 入力ソースの操作は TISInputSource(Carbon)
  • ブラウザのURL判定は Accessibility API
  • 自動切り替えはデバウンスと手動操作との競合処理が肝

みなさんは入力ソースの切り替え、どうやって運用していますか?よければコメントで教えてください 🙌

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?