hnwの日記

一部のCommand+?だけをMeta+?として使うKeyRemap4MacBookパッチ


2008/12/19追記:この記事のパッチには問題がありましたので、修正しました。「Emacs使いが幸せにiTermとTerminal.appを使う方法」を合わせてご覧ください。


先日、アルミになった新MacBookを買いました。やはり新しいマシンはウキウキしますね。僕は今までTigerで耐えてきたので、これが初Leopardです。LeopardのTerminal.appは十分使い物になると聞いていたので、早速試してみることにしました。


ところで、僕はCommandキー(日本語キーボードなら英数キー)がMetaの意味になる端末エミュレータでないとストレスで死んでしまうのですが、これは標準のTerminal.appの機能では実現できません。僕の実現したい環境は次の通りです。

Terminal.app以外
CommandキーはCommandキーとして利用したい。
Terminal.app
Command+FなどはOption+Fにして、シェルにMetaとして渡したい(シェル上で1単語分カーソルを進める)。でも、Command+V(ペースト)やCommand+T(新規タブ作成)など、Terminal.appの機能はそのまま使いたい。


補足しておきますと、多くのシェルではMeta+B,Meta+Fで単語1個分カーソルを前後に移動するといったEmacs風の操作が可能で、シェル上での作業を効率化できます。僕はこれに依存しきっており、使えないとイライラするというわけです。(参考記事:「シェルの操作にAltキーを使おう」)


このわがままを実現する方法ですが、僕はキー割当変更ソフトKeyRemap4MacBookにパッチを当てて解決しました。このパッチはTerminal.appだけでなく、iTermに対しても有効です。

パッチの紹介

今回作ったパッチは、特定のキーとCommandが同時に押された場合にCommandをOptionに置き換えるものです。どんなキーの組み合わせの場合に置き換えるかは下記のようにメニュー形式で選ぶことができます。「Remap Emacs-like Meta key」以下の部分が今回追加した項目です。



僕はCommand+{B,F,D,P,N,W,X}の7つのキーの組み合わせについてはシェルにmeta+{B,F,D,P,N,W,X}として渡しています。また、iTermを併用したい場合のために、Optionではなくescを送るような設定も作ってみました。これで本当に不便が無いかどうか、しばらく実験してみます。


それにしてもKeyRemap4MacBookは素晴らしいツールです。Macを買ってきたら真っ先にインストールしたいソフトウェアの一つではないでしょうか。また、開発環境のXcodeがOSの付属品として無料で手に入るのはMacOSXのナイスな点と言えるでしょう。誰でも気軽に「ちょっとパッチ当ててみるか」と試せるのは実に良い環境ですよね。

パッチの中身

KeyRemap4MacBook 5.1.0に対するパッチを別サーバにアップロードしました(KeyRemap4MacBook-5.1.0-hnw-patch-20081202.txt)。同じ内容を下記にも貼付けておきます。開発バージョンでは既に取り込まれている修正も含んでいます。

diff -ru KeyRemap4MacBook-5.1.0.orig/files/prefpane/checkbox.xml KeyRemap4MacBook-5.1.0/files/prefpane/checkbox.xml
--- KeyRemap4MacBook-5.1.0.orig/files/prefpane/checkbox.xml	2008-05-20 21:00:48.000000000 +0900
+++ KeyRemap4MacBook-5.1.0/files/prefpane/checkbox.xml	2008-12-02 08:21:01.000000000 +0900
@@ -1018,6 +1018,40 @@
               <name>Remap Command_L to Option_L</name>
               <sysctl>remap.app_term_commandL2optionL</sysctl>
             </item>
+            <item>
+              <name>Remap Emacs-like Meta key</name>
+              <list>
+                <item>
+                  <name>Use Escape instead of Option</name>
+                  <appendix>(with below configurations)</appendix>
+                  <sysctl>remap.app_term_emacslike_meta_option2escape</sysctl>
+                </item>
+                <item>
+                  <name>Remap Command_L+[BFD] to Option_L+[BFD]</name>
+                  <sysctl>remap.app_term_emacslike_meta_commandLbfd2optionLbfd</sysctl>
+                </item>
+                <item>
+                  <name>Remap Command_L+[PN] to Option_L+[PN]</name>
+                  <sysctl>remap.app_term_emacslike_meta_commandLpn2optionLpn</sysctl>
+                </item>
+                <item>
+                  <name>Remap Command_L+W to Option_L+W</name>
+                  <sysctl>remap.app_term_emacslike_meta_commandLw2optionLw</sysctl>
+                </item>
+                <item>
+                  <name>Remap Command_L+X to Option_L+X</name>
+                  <sysctl>remap.app_term_emacslike_meta_commandLx2optionLx</sysctl>
+                </item>
+                <item>
+                  <name>Remap Command_L+V to Option_L+V</name>
+                  <sysctl>remap.app_term_emacslike_meta_commandLv2optionLv</sysctl>
+                </item>
+                <item>
+                  <name>Remap Command_L+Q to Option_L+Q</name>
+                  <sysctl>remap.app_term_emacslike_meta_commandLq2optionLq</sysctl>
+                </item>
+              </list>
+            </item>
           </list>
         </item>
       </list>
diff -ru KeyRemap4MacBook-5.1.0.orig/src/core/kext/RemapUtil.cpp KeyRemap4MacBook-5.1.0/src/core/kext/RemapUtil.cpp
--- KeyRemap4MacBook-5.1.0.orig/src/core/kext/RemapUtil.cpp	2008-05-20 20:58:26.000000000 +0900
+++ KeyRemap4MacBook-5.1.0/src/core/kext/RemapUtil.cpp	2008-12-02 08:03:33.000000000 +0900
@@ -871,6 +871,14 @@
   }
 
   void
+  FireFunc::firefunc_before_escape(const RemapParams &params)
+  {
+    unsigned int flags = allFlagStatus.makeFlags(params);
+    listFireExtraKey.add(FireExtraKey::TYPE_BEFORE, KeyEvent::DOWN, flags, KeyCode::ESCAPE, CharCode::ESCAPE);
+    listFireExtraKey.add(FireExtraKey::TYPE_BEFORE, KeyEvent::UP,   flags, KeyCode::ESCAPE, CharCode::ESCAPE);
+  }
+
+  void
   FireFunc::firefunc_space(const RemapParams &params)
   {
     unsigned int flags = allFlagStatus.makeFlags(params);
diff -ru KeyRemap4MacBook-5.1.0.orig/src/core/kext/RemapUtil.hpp KeyRemap4MacBook-5.1.0/src/core/kext/RemapUtil.hpp
--- KeyRemap4MacBook-5.1.0.orig/src/core/kext/RemapUtil.hpp	2008-05-20 20:56:52.000000000 +0900
+++ KeyRemap4MacBook-5.1.0/src/core/kext/RemapUtil.hpp	2008-12-02 08:07:54.000000000 +0900
@@ -259,6 +259,7 @@
     void firefunc_commandSpace(const RemapParams &params);
     void firefunc_enter(const RemapParams &params);
     void firefunc_escape(const RemapParams &params);
+    void firefunc_before_escape(const RemapParams &params);
     void firefunc_space(const RemapParams &params);
     void firefunc_emacsmode_controlK(const RemapParams &params, bool first = false);
     void firefunc_emacsmode_ex_controlU(const RemapParams &params);
diff -ru KeyRemap4MacBook-5.1.0.orig/src/core/kext/remap.cpp KeyRemap4MacBook-5.1.0/src/core/kext/remap.cpp
--- KeyRemap4MacBook-5.1.0.orig/src/core/kext/remap.cpp	2008-05-25 23:46:19.000000000 +0900
+++ KeyRemap4MacBook-5.1.0/src/core/kext/remap.cpp	2008-12-02 08:08:44.000000000 +0900
@@ -1862,6 +1862,54 @@
     }
   }
 
+  void
+  remap_app_term_emacslike_meta(const RemapParams &params)
+  {
+    static ModifierCanceling mc_commandL;
+
+    if (! config.remap_app_term_emacslike_meta_commandLbfd2optionLbfd &&
+        ! config.remap_app_term_emacslike_meta_commandLpn2optionLpn &&
+        ! config.remap_app_term_emacslike_meta_commandLw2optionLw &&
+        ! config.remap_app_term_emacslike_meta_commandLx2optionLx &&
+        ! config.remap_app_term_emacslike_meta_commandLv2optionLv &&
+        ! config.remap_app_term_emacslike_meta_commandLq2optionLq) return;
+
+    if (! (params.activeApplicationInfo)->is_terminal) return;
+
+    if (allFlagStatus.commandL.isHeldDown()) {
+      bool cancel_command = false;
+
+      if ((config.remap_app_term_emacslike_meta_commandLbfd2optionLbfd &&
+           (*(params.key) == KeyCode::B || *(params.key) == KeyCode::F ||
+            *(params.key) == KeyCode::D)) ||
+          (config.remap_app_term_emacslike_meta_commandLpn2optionLpn &&
+           (*(params.key) == KeyCode::P || *(params.key) == KeyCode::N)) ||
+          (config.remap_app_term_emacslike_meta_commandLw2optionLw &&
+           *(params.key) == KeyCode::W) ||
+          (config.remap_app_term_emacslike_meta_commandLx2optionLx &&
+           *(params.key) == KeyCode::X) ||
+          (config.remap_app_term_emacslike_meta_commandLv2optionLv &&
+           *(params.key) == KeyCode::V) ||
+          (config.remap_app_term_emacslike_meta_commandLq2optionLq &&
+           *(params.key) == KeyCode::Q)) {
+
+        if (!config.remap_app_term_emacslike_meta_option2escape) {
+          allFlagStatus.optionL.temporary_increase();
+        }
+        // cancel command_L
+        mc_commandL.keyRelease(params, ModifierFlag::COMMAND_L);
+
+        // メモ:COMMAND_Lを離した後でescのdown/upをする意図。コードが汚くて悲しい。
+        if (config.remap_app_term_emacslike_meta_option2escape &&
+            *(params.eventType) == KeyEvent::DOWN) {
+          FireFunc::firefunc_before_escape(params);
+        }
+        return;
+      }
+    }
+    mc_commandL.restore(params, ModifierFlag::COMMAND_L);
+  }
+
   // ----------------------------------------
   void
   remap_qwerty2colemak(const RemapParams &params)
@@ -2631,6 +2679,7 @@
   // ----------------------------------------
   remap_app_vm_commandspace2optionbackquote(params);
   remap_app_finder_return2commandO(params);
+  remap_app_term_emacslike_meta(params);
 
   // ----------------------------------------
   remap_qwerty2colemak(params);
diff -ru KeyRemap4MacBook-5.1.0.orig/src/core/server/getActiveApplicationName.m KeyRemap4MacBook-5.1.0/src/core/server/getActiveApplicationName.m
--- KeyRemap4MacBook-5.1.0.orig/src/core/server/getActiveApplicationName.m	2008-04-15 01:30:18.000000000 +0900
+++ KeyRemap4MacBook-5.1.0/src/core/server/getActiveApplicationName.m	2008-12-02 08:43:11.000000000 +0900
@@ -1,6 +1,6 @@
 #include <sys/types.h>
 #include <stdio.h>
-#import <Appkit/Appkit.h>
+#import <AppKit/AppKit.h>
 
 void
 getActiveApplicationName(char *buffer, size_t len)
diff -ru KeyRemap4MacBook-5.1.0.orig/src/core/server/server.cpp KeyRemap4MacBook-5.1.0/src/core/server/server.cpp
--- KeyRemap4MacBook-5.1.0.orig/src/core/server/server.cpp	2008-05-20 20:46:13.000000000 +0900
+++ KeyRemap4MacBook-5.1.0/src/core/server/server.cpp	2008-12-02 08:49:33.000000000 +0900
@@ -206,7 +206,8 @@
     reply->is_vi = true;
   }
   if (strcmp(applicationName, "com.apple.Terminal") == 0 ||
-      strcmp(applicationName, "iTerm") == 0) {
+      strcmp(applicationName, "iTerm") == 0 ||
+      strcmp(applicationName, "net.sourceforge.iTerm") == 0) {
     reply->is_terminal = true;
   }
   if (strcmp(applicationName, "com.vmware.fusion") == 0 ||

参考にしたページ