emacsclientでフォーカスを移す

追記 xfce4だとフォーカスを勝手に移してくれます.


最近プログラム関係は Macではなく Ubuntuばっかりなんですが、
emacsclientの使い心地が Macとなにか違う。考えてみると、
フォーカスが移動しないじゃんってことで、なんとかできない
ものかと思い書いてみた。


一応 EmacsWiki: Emacs Clientに書いているんですが、
Emacsのタイトルバーは elscreenのタブ名が表示するようにしているし、
複数立ち上げていることもあるし、端末でもサーバを起動するかもしれないし、
ということで、そのあたりもカバーすることを目標としました。


フォーカスの移動は wmctrlというプログラムを利用します。 Ubuntuの場合
aptからインストールしてください。

  % sudo aptitude install wmctrl

サーバが起動する Emacsの特定

サーバが起動する Emacsを特定するために以下のコードを .emacsに加えました.
defadviceでサーバ起動後に後述の emacs_serverstart.plに起動させます。
引数はその Emacsのプロセス IDと GUI版で起動しているか否かを示すフラグ
です。

(when (string-equal system-type "gnu/linux")
  (defadvice server-start
    (after server-start-after-write-window-id ())
    (call-process "emacs_serverstart.pl"
                  nil nil nil
                  (number-to-string (emacs-pid))
                  (if window-system
                      "x"
                    "nox")))
  (ad-activate 'server-start))

サーバ起動した Emacsの情報を記録する

私の方法は以下のとおりです

  • GUI版では、server-startをした Window IDをファイルに保存
  • GUI版では、ターミナルのウィンドウタイトルを変更


これを実現するために以下の Perlスクリプトを書きました。これを PATHの
通ったディレクトリに置き、実行権限を与えます。

#!/usr/bin/env perl
use strict;
use warnings;

use Fcntl qw(:flock);
use File::Which qw(which);

unless (which("wmctrl")) {
    exit 1;
}

my ($emacs_pid, $has_window) = @ARGV[0,1];
unless ($emacs_pid && $has_window) {
    die "Usage $0 emacs_pid [x or nox]\n"
}

my $server_wid_file = $ENV{HOME} . '/.emacs.d/server_wid';
if ($has_window eq 'x') {
    server_is_x($emacs_pid);
} else {
    server_is_nox();
}

sub server_is_x {
    my $pid = shift;
    my $emacs_window_id;

    open my $fh, "-|", qw/wmctrl -p -l/ or die "Can't open wmctrl process\n";
    while (my $line = <$fh>) {
        chomp $line;
        my ($wid, $pid) = (split ' ', $line)[0,2];

        if ($emacs_pid eq $pid) {
            $emacs_window_id = $wid;
            last;
        }
    }
    close $fh;

    open my $wfh, ">", $server_wid_file or die "Can't open write file\n";
    flock $wfh, LOCK_EX;
    print $wfh $emacs_window_id;
    close $wfh;
}

sub server_is_nox {
    my $server_window_name = 'emacsserver_run';

    unlink $server_wid_file if -e $server_wid_file;

    open my $fh, "-|", qw/wmctrl -l/ or die "Can't open wmctrl process\n";
    while (my $line = <$fh>) {
        chomp $line;
        my $window_title = (split ' ', $line)[3];

        if ($window_title eq $server_window_name) {
            # rename old server run terminal
            my @cmd = (qw/wmctrl -F -r/, $server_window_name, qw/-T Terminal/);
            system();
            last;
        }
    }
    close $fh;

    # change current terminal window
    my @cmd = (qw/wmctrl -r :ACTIVE: -T/, $server_window_name);
    system(@cmd);
}

GUI版では、初めに引数として与えられたプロセス IDから Window IDを特定して
います。得られた Window IDを ~/.emacs.d/server_widというファイルに
書きだしています。複数開かれたことを考えて、ロックをしています。


一方非 GUI版では GUI版で記録した Window IDを削除します。このファイルの
有無で GUI版か非 GUI版のいずれで server-startが実行されたかを判断します。
次にすでに名前が変えられた端末エミュレータがないか調べます。もし一致する
ものがあれば、それを 'Terminal'に変更します。そして最後に現在アクティブ
なウィンドウの名前をサーバが起動していることを示す名前に変更します。


これでフォーカスを移すための情報が揃いました。

フォーカスを切り替える emacsclient

フォーカルを切り替えるために以下のシェルスクリプトを書きました。
これも PATHの通ったディレクトリに置き、実行権限を与えます。

#!/bin/sh

if ! which wmctrl > /dev/null 2>&1
then
    echo "Please install wmctrl"
    exit 1
fi

# get current window id
CURRENT_WID=`wmctrl -a :ACTIVE: -v 2>&1 | \
    perl -wln -e 'm{Using window: (\w+)$} and print $1'` 2> /dev/null
if [ "$CURRENT_WID" = "" ]
then
    echo "Faild getting current window id"
    exit 1
fi

WID_FILE=${HOME}/.emacs.d/server_wid

if [ -e $WID_FILE ]
then
    # server is running on GUI Emacs
    WID=`cat $WID_FILE`
    # forcus emacs server window
    wmctrl -i -a $WID
else
    # server is running emacs in terminal
    SERVER_TERM_TITLE='emacsserver_run'
    wmctrl -F -a $SERVER_TERM_TITLE
fi

emacsclient $@

# return to origin window
wmctrl -i -a $CURRENT_WID

初めに編集が終わった後にフォーカスを元に戻すために、現在の WindowIDを
取得しています。そして次に切り替えですが、GUI版用のファイルがあれば
GUI版、なければ非GUI版としています。フォーカスの切り替えは '-a'
オプションです。なお'-i'は WindowIDで指定するためのオプション、
'-F'は厳密にタイトルバーと一致させるためのオプションです。


フォーカスが終わったら emacsclientを起動して、編集が終われば
元のウィンドウに戻ります。


使い方は emacsclientから上記のシェルスクリプトに変更するだけです。

  % emacsclient.sh ファイル名

これで目標が達成できました。手元のテストでは GUI版でも端末版でも
動きました。問題があればご指摘いただけると助かります。

最後に

休みになったらもうちょっと確認して githubに上げます。

追記

githubに上げました。no-waitの問題に対応しました。-nオプションだけで、

    • no-waitと指定されると無理なんですがね。

syohex/emacsclient_focus · GitHub