emacs-plusをHomebrewでインストールして使っている方なら、emacsclientでサーバーに接続して素早くEmacsを開きたいと思うはずです。
ただ、LaunchPadから起動すると、ウィンドウがアクティブにならない問題に遭遇しました。
1. emacsclientがフォーカスされない
emacs-plusをインストールすると、Emacs daemonを起動してemacsclientで接続する使い方ができます1。
ターミナルからemacsclient -cを実行すれば新しいフレーム(ウィンドウ)が開きますが、macOSのLaunchPadやSpotlightから起動すると、ウィンドウは開いても前面に来ないという問題がありました。
2. スクリプトの仕組み
結論から言うと、AppleScriptでアプリケーションを作るのが最適でした。

Script Editor(スクリプトエディタ)を開いて、以下のスクリプトを書きます2。
do shell script "
EMACSCLIENT='/Applications/Emacs.app/Contents/MacOS/bin/emacsclient'
EMACS='/Applications/Emacs.app/Contents/MacOS/Emacs'
server_alive() {
\"$EMACSCLIENT\" -e '(progn t)' >/dev/null 2>&1
}
# 1) デーモンがいなければ起動
if ! server_alive; then
\"$EMACS\" --daemon >/dev/null 2>&1 &
# 起動待ち(最大3秒)
i=0
while ! server_alive; do
i=$((i+1))
[ $i -ge 30 ] && break
sleep 0.1
done
fi
# 2) それでもダメならエラー終了(アラートに出る)
if ! server_alive; then
echo 'Error: Could not start the Emacs daemon.' 1>&2
exit 1
fi
# 3) 既存フレームがあればフォーカス、なければ新規フレーム作成
FRAME_COUNT=$(\"$EMACSCLIENT\" -e '(length (frame-list))' 2>/dev/null | tr -d '\"' || echo 1)
if [ \"$FRAME_COUNT\" -gt 1 ]; then
\"$EMACSCLIENT\" -n -e '(select-frame-set-input-focus (selected-frame))' >/dev/null 2>&1
else
\"$EMACSCLIENT\" -n -c >/dev/null 2>&1
fi
"
tell application "System Events"
repeat 50 times
set candidates to (processes whose name contains "Emacs")
repeat with p in candidates
if (count of windows of p) > 0 then
set frontmost of p to true
return
end if
end repeat
delay 0.1
end repeat
end tell
Code language: PHP (php)
ファイルメニューから「書き出す」を選び、ファイルフォーマットを「アプリケーション」にして、Applicationsフォルダに保存します。
名前は入力しやすいように、「Emma.app」としました。

このスクリプトは、3つのステップで動いています。
2.1. GUIフレームの存在確認
当初は、既にウィンドウが開いているのに新しいフレームが追加で作成されてしまう問題がありました。emacsclient -cは無条件に新しいフレームを作るので、既存ウィンドウの有無を確認する必要があったのです。
そこで、「デーモンが生きているか」を先に判定し、死んでいたら Emacs --daemon で起動してます。
そして、emacsclient -e '(length (frame-list))'で、現在開いているEmacsのフレーム数を取得します3。
Emacs daemonだけが起動している状態だとフレーム数は1ですが、GUIウィンドウが開いていれば2以上になります。
ここがポイントで、単にdaemonが起動しているかだけでなく、実際にウィンドウが開いているかを判定しています。
2.2. 既存フレームの再利用か新規作成か
フレーム数が2以上なら既存のウィンドウがあるので、select-frame-set-input-focusでフォーカスを当てるだけです。
フレーム数が1以下なら、-cオプションで新しいGUIフレームを作成します4。
-a ''オプションは、daemonが起動していない場合に自動で起動してくれる保険です。
2.3. macOSでウィンドウを前面に
最後に、AppleScriptのSystem Eventsを使ってEmacsプロセスを前面に持ってきます。
これで確実にウィンドウがアクティブになります。
tell application "System Events"
repeat 50 times
set candidates to (processes whose name contains "Emacs")
repeat with p in candidates
if (count of windows of p) > 0 then
set frontmost of p to true
return
end if
end repeat
delay 0.1
end repeat
end tell
Code language: PHP (php)
System Events では Emacs プロセスが複数存在し得るため、first process に依存するとウィンドウのないサーバを掴む可能性があります。
そのため、まず name で候補プロセスを列挙し、各プロセスの windows 数を個別に確認すします。
ウィンドウを持つプロセスが見つかった時点で前面化することで、レースや型エラーを回避しました。
あと、アクセシビリティの権限がいるので、スクリプトエディタで変更後には、改めてアクセシビリティの一覧からアプリを消して、追加し直す必要があります。
3. 注意点:emacsclientのパス
スクリプト内では、 /Applications/Emacs.app/Contents/MacOS/bin/emacsclientとフルパスを指定しています5。
これはemacs-plusの標準的なインストール場所ですが、環境によって異なる場合があります。
ターミナルでwhich emacsclientを実行して、正しいパスを確認してください。
Intel MacとApple Siliconでも場所が違うことがあります。
3.1. emma.appのアイコンを変更する
Emacsのアイコンを、emma.appにも適用しました。
# アイコンのパスを探す
find_icon() {
local p
for p in \
"/Applications/Emacs Client.app/Contents/Resources/applet.icns" \
"/Applications/Emacs.app/Contents/Resources/Emacs.icns" \
"/Applications/Emacs.app/Contents/Resources/applet.icns"
do
[[ -f "$p" ]] && { printf '%s\n' "$p"; return 0; }
done
if command -v brew >/dev/null 2>&1; then
local brew_prefix
brew_prefix="$(brew --prefix 2>/dev/null || true)"
p="$(find "${brew_prefix}" -type f \
\( -path '*/Emacs Client.app/Contents/Resources/applet.icns' \
-o -path '*/Emacs.app/Contents/Resources/Emacs.icns' \
-o -path '*/Emacs.app/Contents/Resources/applet.icns' \) \
2>/dev/null | head -n 1 || true)"
[[ -n "${p}" ]] && { printf '%s\n' "$p"; return 0; }
fi
return 1
}
APP_PATH="/Applications/Emma.app"
PLIST="${APP_PATH}/Contents/Info.plist"
ICON_SRC="$(find_icon)"
cp "${ICON_SRC}" "${APP_PATH}/Contents/Resources/applet.icns"
/usr/libexec/PlistBuddy -c "Set :CFBundleIconFile applet" "${PLIST}" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleIconFile string applet" "${PLIST}"
codesign --force --deep --sign - "${APP_PATH}" >/dev/null 2>&1 || true
touch "${APP_PATH}"
echo "完了: ${ICON_SRC} → ${APP_PATH}"Code language: Bash (bash)
3.2. 「補助アクセス」のアクセシビリティ権限
「補助アクセス」のアクセシビリティ権限が必要なので、「システム設定」から許可をします。


3.3. さらなる高速化:daemon自動起動
起動速度をさらに改善したい場合は、ログイン時にEmacs daemonを自動起動する設定が有効です6。
~/Library/LaunchAgents/gnu.emacs.daemon.plistを作成して、以下の内容を記述します。
<?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>gnu.emacs.daemon</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Emacs.app/Contents/MacOS/Emacs</string>
<string>--daemon</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>Code language: HTML, XML (xml)
ターミナルで以下を実行して有効化します7。
launchctl load ~/Library/LaunchAgents/gnu.emacs.daemon.plistCode language: JavaScript (javascript)
これでログイン時にdaemonが起動し、emacsclientの接続がほぼ瞬時に完了します。
4. 【補足】Automatorでラッパーを作る案
ちなみに、まず思いついたのは、Automatorでシェルスクリプトを実行するアプリを作ることでした。emacsclientを呼び出して、その後macOSのosascriptコマンドでEmacsをアクティブにする方法です。


#!/bin/bash
export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
# 既存のGUIフレームがあるか確認
if pgrep -x "Emacs" > /dev/null && emacsclient -e "(> (length (frame-list)) 1)" 2>/dev/null | grep -q "t"; then
# 既存フレームにフォーカス
osascript -e 'tell application "Emacs" to activate'
emacsclient -n -e '(select-frame-set-input-focus (selected-frame))'
else
# 新規フレーム作成
emacsclient -c -n -a "" -e '(select-frame-set-input-focus (selected-frame))'
fiCode language: PHP (php)
ただ、これには2つの問題がありました。
LaunchPadから起動すると、ターミナルのようにPATH環境変数が設定されていません。emacsclientがどこにあるか分からず、コマンドが見つからないエラーになります。
また、Automator自体の起動に時間がかかり、体感で0.5〜1秒ほどの遅延が発生します8。
せっかく軽量なemacsclientを使っているのに、これでは意味がありません。
- emacs-plusは公式のGNU EmacsにmacOS向けの機能拡張を加えたHomebrewフォーミュラです。ネイティブコンパイル、SVGサポート、Retina対応などの機能が含まれています。 – emacs-plus GitHub Repository
- Script EditorはmacOSに標準搭載されているAppleScriptの開発環境です。アプリケーションフォルダのユーティリティフォルダ内、またはSpotlight検索で「Script Editor」と入力すると起動できます。
frame-listはEmacs Lispの組み込み関数で、現在存在する全てのフレーム(ウィンドウ)のリストを返します。daemon起動時には不可視のフレームが1つ作成されるため、GUIフレームがない状態でもカウントは1になります。 – Emacs Lisp Reference Manual – Framesselect-frame-set-input-focusはEmacsのフレームを選択し、ウィンドウマネージャーレベルでフォーカスを設定する関数です。macOSのウィンドウシステムと連携して、確実にフレームをアクティブにします。 – Emacs Lisp Reference Manual – Input Focus- LaunchPadやSpotlightから起動されたアプリケーションは、ターミナルと異なり環境変数PATHが正しく設定されていません。そのため、
which emacsclientで表示されるパスをフルパスで指定する必要があります。Intel MacとApple Siliconでは、Homebrewのインストール場所が異なる点にも注意が必要です(Intel:/usr/local/bin、Apple Silicon:/opt/homebrew/bin)。 - LaunchAgentsはmacOSのlaunchd機構を使った自動起動の仕組みです。ユーザーがログインした時点でプログラムをバックグラウンドで起動し、常駐させることができます。 – Apple Developer – Creating Launch Daemons and Agents
launchctl loadコマンドでplistファイルを読み込むと、即座にdaemonが起動し、次回ログイン時からも自動的に起動するようになります。無効化したい場合はlaunchctl unloadコマンドを使用します。また、macOS Ventura以降ではlaunchctl bootstrapコマンドの使用が推奨されています。- Automatorは手軽にワークフローを作成できる反面、起動オーバーヘッドが大きいという欠点があります。AppleScriptで直接アプリケーションを作成する方が軽量で、起動時間を大幅に短縮できます。シンプルなタスクであればAppleScript、複雑なワークフローが必要な場合はAutomatorと使い分けるのが良いでしょう。