■ Linux の emacs で利用できる設定


【お知らせ】


<2019/05/07 追記>
現在は cygwin の代わりに WSL を使うことで同様な環境を構築できます。そもそも WSL があれば、リモートマシンで emacs を起動する必要は無いのかもしれません。

<2017/05/01 追記>
dired のソート順を制御する ls-lisp-UCA-like-collation の設定を追加しました。

<2016/09/29 追記>
os-open-command 関数をファイルとディレクトリのみ(URLは対象外)に機能する関数として見直しました。この見直しにより、os-open-command 関数に指定したファイルとディレクトリを開くコマンドを起動するホストの決定は、第一引数に指定した内容からのみで(default-directory を使わずに)判断するようにしました。(「VirtualBox 上の Ubuntu から WSL を経由して Windows 環境にアクセスするための設定」を利用するための見直しです。)
(os-open-command "/home/foo.pdf") ← ローカルホストで実行
(os-open-command "/user@hostname:/home/foo.pdf") ← hostname に指定したホストで実行 

【本題】


Windows 上の仮想マシン環境(VirtualBox等)で Linux(Ubuntu等) を利用している場合に、その環境で立ち上げる emacs を使ってローカルマシンの
Windowsアプリケーションを起動させるための設定です。Putty などで接続した、リモートマシン上の Linux を使うこともできます。

オプションとして、ローカル環境で Xサーバが動作している場合(VirtualBox で Linux を立ち上げた場合など)で、Tramp で接続する先がさらに
Linux の場合、ssh 経由の X11 forwarding が動作するように設定しています。
(リモートのマシンで Xクライアント を起動すると、ssh のトンネルを利用してローカルマシンに表示されます。)

<Windowsアプリケーションを起動する場合の使い方>
1) 利用している Windows マシンから、VirtualBox や Putty などで Linux環境を使えるようにする。
2) そこで emacs を立ち上げ、ローカルマシンの Windows で動いている cygwin に tramp で dired 接続する。
  C-x d /username@WindowsIPアドレス: (リターン)
3) Windows で開きたいディレクトリやファイルにカーソルを合わせ、Wキー を入力する。⇒ Windows アプリケーションで開く!
4) dired で開いているフォルダを Explorer で開きたければ、Eキー を入力する。⇒ Explorer で開く!
5) 以下の設定の os-open-file-suffixes変数 に設定してある suffix が付いたファイルであれば、fキー で起動できる。
  Wキー や fキー でファイルをオープンした場合は、recentf に登録(recentf-push)するようにしているので、以降 recentf の履歴から直接起動できる。
  (helm で recentf の情報源を表示するようにしている場合は、便利)

以下の内容は、Windows 7 を利用している PC での設定手順となります。



0) ローカルマシンに cygwin がインストールされていなければ、インストールする。

1) cygwin に sshd がインストールされていなければ(/usr/sbin/sshd があればインストール済み)、インストールする。

2) cygwin のターミナルを管理者権限で起動する。(右クリックで「管理者として実行」を選択)

3) 既にサービスとして sshd 立ち上げている場合(ps -ef | grep sshd で確認できる)は、以下を実行する。
 (サービスとして立ち上げているかわからないときは、とりあえず以下を実行すればOK)
# cygrunsrv -R sshd

4) 既に sshd の設定がされている場合(/etc/ssh* があれば設定済み)は、以下を実行する。
  (消すファイルは、/bin/ssh* でないことに注意!!)
# rm /etc/ssh* /var/log/sshd.log
# rmdir /var/empty

5) 引き続き、以下を実行する。
# ssh-host-config

*** Info: Generating /etc/ssh_host_key
*** Info: Generating /etc/ssh_host_rsa_key
*** Info: Generating /etc/ssh_host_dsa_key
*** Info: Generating /etc/ssh_host_ecdsa_key
*** Info: Creating default /etc/ssh_config file
*** Info: Creating default /etc/sshd_config file
*** Info: Privilege separation is set to yes by default since OpenSSH 3.3.
*** Info: However, this requires a non-privileged account called 'sshd'.
*** Info: For more info on privilege separation read /usr/share/doc/openssh/README.privsep.
*** Query: Should privilege separation be used? (yes/no) yes
*** Info: Updating /etc/sshd_config file
*** Query: Overwrite existing /etc/inetd.d/sshd-inetd file? (yes/no) yes
*** Info: Creating default /etc/inetd.d/sshd-inetd file
*** Info: Updated /etc/inetd.d/sshd-inetd

*** Query: Do you want to install sshd as a service?
*** Query: (Say "no" if it is already installed as a service) (yes/no) no ← ここまでとする!

*** Info: Host configuration finished. Have fun!

6) cygwin のターミナルを終了する。

7) cygwin のターミナルを普通に(管理者権限でなく)起動する。

8) /usr/sbin/sshd と入力し、sshd を起動する。

※ sshd は Windows を再起動する都度、起動する必要がある。
  ssh-host-config の設定で ssh をサービスとして登録して立ち上げる方法もあるが、このようにして立ち上げた ssh では cygstart がうまく動かない。
  cyglsa-config の設定をし、sshd のサービスの設定でログインアカウントをLSAに変更して「デスクトップとの対話をサービスに許可」のチェックを
  入れてみたが、cygstart でディレクトリは開くがアプリケーションは起動しないという状況だった。今のところ、対応策を見つけられていない。

9) ssh localhost でローカルマシンにログインする。

 ※ 接続できない場合は、Windowsファイアウォールの設定で TCP:22番のポートを開く必要があるかもしれない。
   また、ssh の関連ファイルを削除し、ssh の再設定(ssh-host-config)を行った場合は、以下のようなメッセージが出るかもしれない。
   その場合は、~/.ssh/known_hosts ファイルを削除する。(他のマシンから cygwin へアクセスする際に発生したら、同様に対処する)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx.
Please contact your system administrator.
Add correct host key in /home/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /home/.ssh/known_hosts:1
ECDSA host key for xxx.xxx.xxx.xxx has changed and you have requested strict checking.
Host key verification failed.

10) cygstart . (← ピリオド(カレントディレクトリ)です。/(ルート)などでもいいです)で Explorer が起動することを確認する。
  また、Windows アプリケーションとの結びつきをもつ suffix をもったファイルが cygstart で起動できるか確認する。(例:cygstart hoge.xlsx)

11) ここまで来たら、以下のことが可能となる。
  • リモートマシンで emacs を起動する。(仮想マシン上の Linux でも良い)
  • dired で /ユーザ名@ローカルマシンのIPアドレス: を指定し、ローカルマシンのディレクトリをオープンする。
  • ディレクトリや Windows のアプリケーションで開ける suffix を持ったファイルにカーソルを合わせ、X(dired-do-shell-command)を入力し、プロンプトに cygstart と入力し、リターン押下。
  • ローカルマシンの Windows のアプリケーションで 指定したファイルが開く!筈

12) ここまでもうまく動いたら、以下の設定を追加してみる。

以下のページで紹介している設定の一部を emacs の設定に追加し、有効にする。
(require 'cl-lib)
(require 'dired)
(require 'ls-lisp)
(require 'recentf)
(require 'tramp)
(require 'tramp-sh)

;; ls-lisp を使う
(setq ls-lisp-use-insert-directory-program nil)

;; dired の並び順を explorer と同じにする
(setq ls-lisp-ignore-case t)          ; ファイル名の大文字小文字無視でソート
(setq ls-lisp-dirs-first t)           ; ディレクトリとファイルを分けて表示
(setq dired-listing-switches "-alG")  ; グループ表示なし
(setq ls-lisp-UCA-like-collation nil) ; for 25.1 or later

;; OSタイプ を調べる
(defun os-type ()
  (let ((os-type (shell-command-to-string "uname")))
    (cond ((string-match "CYGWIN" os-type)
           'cygwin)
          ((string-match "Linux" os-type)
           'linux)
          ((string-match "Darwin" os-type)
           'darwin))))

;; OS でファイル、ディレクトリを直接開くためのコマンドを決定する
(defun os-open-command-name (os-type)
  (let ((command-name-list (cl-case os-type
                             ('cygwin
                              '("sglstart" "cygstart"))
                             ('linux
                              '("sglstart" "wslstart" "xdg-open" "gnome-open"))
                             ('darwin
                              '("open")))))
    (catch 'loop
      (dolist (command-name command-name-list)
        (let* ((command1 (concat "which " command-name " 2> /dev/null"))
               (command2 (if (file-remote-p default-directory)
                             ;; リモートではログインシェルでコマンドを実行する
                             (format "$0 -l -c '%s' 2> /dev/null" command1)
                           command1))
               (absolute-path-command-name (replace-regexp-in-string
                                            "\n" ""
                                            (shell-command-to-string command2))))
          (unless (string= absolute-path-command-name "")
            (throw 'loop absolute-path-command-name)))))))

;; os-open-command のキャッシュ
(defvar os-open-command-cache nil)

;; キャッシュを検索・登録する
(defun os-open-command-cache ()
  (let* ((hostname (if (file-remote-p default-directory)
                       (let* ((vec (tramp-dissect-file-name default-directory))
                              (host (tramp-file-name-host vec))
                              (user (tramp-file-name-user vec)))
                         (if user
                             (format "%s@%s" user host)
                           host))
                     "<localhost>")))
    (cdr (or (assoc hostname os-open-command-cache)
             (let* ((os-type (os-type))
                    (os-open-command-name (os-open-command-name os-type)))
               (car (push (cons hostname (list os-type os-open-command-name))
                          os-open-command-cache)))))))

;; OS で直接、ファイル、ディレクトリを開く
(defun os-open-command (filename)
  (interactive)
  (let ((default-directory (cond ((file-regular-p filename)
                                  (file-name-directory filename))
                                 ((file-directory-p filename)
                                  filename))))
    (if default-directory
        (let* ((cache (os-open-command-cache))
               (os-type (nth 0 cache))
               (os-open-command-name (nth 1 cache)))
          (if os-open-command-name
              (let ((localname (if (file-remote-p filename)
                                   (tramp-file-name-localname
                                    (tramp-dissect-file-name filename))
                                 filename)))
                (message "%s %s" (file-name-nondirectory os-open-command-name) localname)
                (cond ((and (eq os-type 'linux)
                            (not (file-remote-p default-directory)))
                       ;; 以下の URL の対策を行う
                       ;; http://d.hatena.ne.jp/mooz/20100915/p1
                       ;; http://i-yt.info/?date=20090829#p01
                       (let (process-connection-type)
                         (start-process "os-open-command" nil os-open-command-name localname)))
                      (t
                       ;; リモートでもコマンドを実行できるように、start-process ではなく shell-command系を使う
                       (shell-command-to-string (concat os-open-command-name " "
                                                        (shell-quote-argument localname) " &")))))
            (message "利用できるコマンドがありません。")))
      (message "オープンできるファイルではありません。"))))

;; dired で W 押下時に、カーソル位置のファイルを OS で直接起動する
(define-key dired-mode-map (kbd "W")
  (lambda ()
    (interactive)
    (let ((filename (dired-get-filename nil t)))
      (recentf-push filename) ; recentf に追加する
      (os-open-command filename))))

;; dired で E 押下時に、開いているディレクトリを OS で直接開く
(define-key dired-mode-map (kbd "E")
  (lambda ()
    (interactive)
    (os-open-command (dired-current-directory))))

;; マウスの右ボタン押下時に、開いているディレクトリを OS で直接開く
(define-key dired-mode-map [mouse-3]
  (lambda (event)
    (interactive "e")
    (mouse-select-window event)
    (os-open-command (dired-current-directory))))

;; OS で起動したいファイルの拡張子一覧
(setq os-open-file-suffixes '("doc" "docx"
                              "xls" "xlsx"
                              "ppt" "pptx"
                              "mdb" "mdbx"
                              "vsd" "vdx" "vsdx"
                              "mpp"
                              "pdf"
                              "bmp" "jpg"
                              "odt" "ott"
                              "odg" "otg"
                              "odp" "otp"
                              "ods" "ots"
                              "odf"
                              ))

;; OS で直接開きたいファイルかどうかを判定する
(defun os-open-file-p (filename)
  (when (file-regular-p filename)
    (let ((ext (file-name-extension filename)))
      (when (and ext
                 (member (downcase ext) os-open-file-suffixes))
        t))))

;; dired でファイルを f で開く際に、os-open-file-suffixes リストに指定してあるサフィックスのファイルは OS で直接起動する
(advice-add 'find-file
            :around (lambda (orig-fun &rest args)
                      (let* ((file-name (nth 0 args))
                             (symlink-name (file-symlink-p file-name))
                             (target-name (if symlink-name
                                              symlink-name
                                            file-name)))
                        (cond ((os-open-file-p target-name)
                               (let ((filename (expand-file-name file-name)))
                                 (recentf-push filename) ; recentf に追加する
                                 (os-open-command filename)))
                              (t
                               (apply orig-fun args))))))

;; tramp の method を設定する
(setq tramp-default-method "scp")

;; リモートサーバで shell を開いた時に日本語が文字化けしないよう、LC_ALL と LC_CTYPE の設定を無効にする
;; http://www.gnu.org/software/emacs/manual/html_node/tramp/Remote-processes.html#Running%20a%20debugger%20on%20a%20remote%20host
(let ((process-environment tramp-remote-process-environment))
  (setenv "LC_ALL" nil)
  (setenv "LC_CTYPE" nil)
  (setq tramp-remote-process-environment process-environment))
以下のページで紹介している設定も行う。

 この設定で、cygwin に接続している dired のバッファで、Wキー や Eキー や fキー を押下することで、Windows アプリケーションが開くようになるはず。
 ここまで来ると、helm の recentf に登録されている cygwin 上のファイル(fキーで起動したものが、recentf に登録される)を選択するだけで起動するようになる。

13) 最後に、必要であれば emacs を立ち上げるマシンの公開鍵を 接続する cygwinマシン(ローカルマシン)に登録する。設定方法は以下の通り。
$ ssh-copy-id -i ~/.ssh/id_rsa.pub <ユーザID>@<ローカルマシンのIPアドレス>


<変更履歴>
  • 2013/11/09 このページを作成した。
  • 2013/11/11 ls-lisp を使うように変更した。
  • 2013/11/15 OSソフトの起動方法の見直しを反映した。
  • 2013/11/17 Linux マシン に接続した際に、ssh 経由で X11 fowarding が有効になるようにした。
  • 2013/11/18 os-open-command-name function の内容を見直した。
  • 2013/11/18 os-open-command function の内部の判定が誤っていたので訂正した。
  • 2014/05/10 tramp による scp 実行時に圧縮オプションが付くようにした。
  • 2014/12/08 emacs-24.4 では scpc method は提供されていないようなので、tramp-method を scp にした。
  • 2015/06/19 linux で os-open-command を起動する際の判定方法を見直した。
  • 2015/09/13 advice を emacs-24.4 以降の書式に見直した。
  • 2016/09/29 os-open-command 関数をファイルとディレクトリのみ(URLは対象外)に機能する関数として見直した。
  • 2016/10/05 os-open-command 関数が一度検索した接続先毎の利用コマンドをキャッシュして再利用するように改善した。
  • 2016/10/06 コマンドのキャッシュのキーを <ホスト名> から <ユーザ名>@<ホスト名> に変更した。
  • 2016/11/19 ローカル接続(tramp 接続でない)の場合の os-open-command-cache のキーを "localhost" から "<localhost>" に変更した。
  • 2017/04/09 find-file のアドバイスを一部見直した。(os-open-command コマンドの引数が絶対パスとなるように調整した。)
  • 2017/04/20 find-file のアドバイスを一部見直した。(リンク先のファイルの種別を判定できるように対策した。)
  • 2017/05/01 dired のソート順を制御する ls-lisp-UCA-like-collation の設定を追加した。