emacsclient の使い方の種類と、便利な使い方

前回の記事からわかるように emacsclient にハマっている。前回も軽く触れている通り、emacsclient、emacs サーバ機能には2通りの使い方があると思う。ちなみに前回は、後者に関しての問題(文字化け)でした。

  • 母艦派: 一つの emacs を立ち上げっぱなし
    • M-x server-start もしくは、.emacs に(server-start) でサーバ化
    • emacsclient -n filename でサーバに表示させ、サーバで閲覧編集する
    • 認識: 母艦でファイル開くときに、ファイルパスを入力する必要がなくなる!
    • 補足: オプション -n がないと、母艦で編集終了の操作 C-x C-# されるまで、端末に「ちょっと待っててね!」という旨のメッセージが表示され操作できません。
  • デーモン派: デーモンとして使う
    • emacs --daemon でデーモンとして起動
    • → emacsclient -c -t で現在の端末にemacs を表示し、閲覧編集する
    • 認識: 高速で起動する emacs だ!

emacsclient を便利に使いたいとき

emacsclient を便利に使いたいならこんな感じに成るんでしょうかね。

母艦派

alias を割り当てるだけ(?)

alias e="emacsclient -n"
# alias emacs="emacsclient -n" 
# alias vi="emacsclient -n" # これはどうなの。。。

デーモン派

こちらは、既存のコマンド emacs の置換になるはずなので、少し複雑になります。サーバの立ち上げなどをシームレスに行うための処理が欲しくなりますしね。僕の .zshrc はこんな感じで落ち着きました。

## emacsclient をシームレスに使うための関数
## http://k-ui.jp/?p=243
function e(){
    echo "[$0] emacsclient -c -t $*";
    (emacsclient -c -t $* ||
        (echo "[$0] emacs --daemon"; emacs --daemon &&
            (echo "[$0] emacsclient -c -t $*"; emacsclient -c -t $*)) ||
        (echo "[$0] emacs $*"; emacs $*))
}
 
# ソケットの場所を環境変数に覚えてもらう
# emacs のバージョンによって少し場所が違うようなので、
# *** "/tmp" を要確認 ***
export USER_ID=`id -u`
export EMACS_TMP_DIR="/tmp/emacs$USER_ID"
export EMACS_SOCK="$EMACS_TMP_DIR/server"
 
## screen emacsclient をシームレスに使うための関数
function se(){
    if which emacsclient &&
        (echo "[$0] ls $EMACS_SOCK "; ls $EMACS_SOCK) ||
        (echo "[$0] emacs --daemon"; emacs --daemon)
    then
        echo "[$0] screen -t emacs emacsclient -t -c $*";
        screen -t emacs emacsclient -t -c $*
    elif which emacs
    then
        echo "[$0] screen emacs -t -c $*";
        screen emacs -t -c $*
    fi
 
}
 
##  $EMACS_TMP_DIR が無いとき
if ! [ -d $EMACS_TMP_DIR ]; then
 
   #(socket 使わないバージョン、毎回emacs--daemonしてる。。。)
    function se(){
        if which emacsclient
        then
            echo "[$0] emacs --daemon"
            emacs --daemon
            echo "[$0] screen -t emacs emacsclient -t -c $*"
            screen -t emacs emacsclient -t -c $*
        elif which emacs
        then
            echo "[$0] screen emacs -t -c $*";
            screen emacs -t -c $*
        fi
    }
fi

emacsclient -n -c がダメなら、emacs --daemon した後、emacsclient -n -c しています。screen 使うときはそうもいかないので、デーモンと通信するためのソケットが存在するかどうか確認をするようになっています。コメントにも書いてありますが、バージョンによってソケットの場所と名前がビミョーに違うので、確認したほうが良いかもしれません。

僕はデーモン派なので、母艦派に関する使い方がどう考えても甘いですね。

emacsclient の文字化け解決法

ふつーの emacs で起動しても文字化けしないのに、emacs --daemon で emacs サーバを立ち上げて、emacsclient -t somefile すると、文字が化けてしまう問題の解決方法。下記を .emacs などに追加する。

;; emacsclient でアクセスした時の文字コード設定
;; バグ: "emacsclient -c" で起動すると実行されない
(add-hook 'server-visit-hook
          (lambda ()
            (set-terminal-coding-system 'utf-8)
            (set-keyboard-coding-system 'utf-8)
            ))

補足: 上記の問題点など

少し読める人はわかるかもしれないけれど、本来ふつーに書くべき、(set-terminal-coding-system 'utf-8)(set-terminal-coding-system 'utf-8) を、emacsclient がサーバに接続したときにもう一度発動するようになっている。どうやら、表示する端末ごとにこれらの設定が必要らしい。

しかしながら、上記のコメントにも書いてあるが、emacsclient -t -c ファイル指定無しで開くとやはり文字化けが起きてしまう。残念ながらファイル指定無し起動は、適切な hook が無いよう。なので現状はどうしようもないのかな。。。

そもそも、この原因が起きる人は、emacsclient -t で、今の端末に emacs を表示する人たちである。しかし、

  • ファイル名を指定して実行しないといけない
  • オプション -t を付けないと、現在の端末では開けない

っていう仕様から察するに、このemacs サーバ/emacsclient は、母艦となる emacs をサーバ化して、そこでバッファを一元管理しようってのがそもそも意図なんだろうな。

ぐぐっても全然困ってる人がいないので、おかしいなーと思ったのだけれど、そもそもあまり行儀の良い使い方ではなかった様子。

Emacs から WordPress に投稿+α

Emacs から WordPress に投稿するための Elisp (wp-emacs) があるようなので試してみた。

  1. ファイルのダウンロードし、パスが通っている場所に配置をする。

    svn の リポジトリ から、下の二つをダウンロードする

  2. .emacs(もしくは .emacs.el)に下記の記述を追加
    (require 'weblogger)
      ;; (global-set-key "\C-cbs" 'weblogger-start-entry) ;; C-c b s とタイプすると新規作成
  3. wordpress の設定を変更

    標準のままだと、XML-RPC が使えない。下記の様に辿り wordpress の設定を変更する。

    ダッシュボード → 設定(左上辺り?)→ 投稿設定 → XML-RPC の項目にチェックを入れる
  4. M-x weblogger-setup-weblog とタイプし、server-url, user name, password を設定
    server-url は、http://(サーバー名)/(wordpress のトップまでのパス)/xmlrpc/
  5. M-x weblogger-start-entry とタイプすると、新しい記事を作成できる

    あるいは、先程の ;; (grobal-setkey ... をコメントから外すならば、C-c b s で新しい記事を作成できる

これで基本的な設定はおしまい。ただしこれだと、記事を編集したいだけの時に、具合が良くない。 M-x weblogger-fetch-entries でできるはずなのだが、ユーザー情報の読み込みが上手くいかない。次の”+α”で、その他いくつかの問題点を解決している。

+α

wordpress で、emacs を立ち上げ wordpress の編集をできるようにするコマンド(正確にはalias)を作る。ついでに wordpress_new ってコマンドで新規記事の作成もできるようにする。
上記に書いた以外に、●時々ユーザ設定を正しく読み込まない、●うっかり C-x C-s すると記事が公開されてしまう、●起動のたびにパスワードを入力する必要がある、●emacs 立ち上げてから weblogger を立ち上げるの面倒、●weblogger.el が無い環境だと起動に失敗する、といった問題がある。これらの問題を .emacs と .zshrc に数行書き加えて解決する 。

まずは、.emacs の先程書いた2行を下記に置き換える

(require 'weblogger nil t) ;; weblogger.el がないときロードしない
(if (featurep 'weblogger) ;; weblogger.el があるときだけ以下を実行する
    (let ()
      ;; (global-set-key "\C-cbs" 'weblogger-start-entry) ;; C-c b s で新規作成
 
      ;; C-x C-s を記事の保存だけする
      (define-key weblogger-entry-mode-map "\C-x\C-s" 'weblogger-save-entry)
 
      ;; C-c C-c で記事の保存 → 公開をする。また公開中の記事は、非公開になる。
      (define-key weblogger-entry-mode-map "\C-c\C-c" 'weblogger-publish-entry)
 
      ;; weblogger-setup-weblog で自動生成されたユーザ情報
      (custom-set-variables
       '(weblogger-config-alist
	 (quote (("default" ;; ここの "default" は、下の起動オプション用関数に使う。人に依っては別の文字で与えてるかもしれない
		  ("user" . "k-ui")
		  ("pass" . "パスワードだよ") ;; "pass" を設定することでパスワードの入力せずに起動できる
		  ("server-url" . "http://k-ui.jp/xmlrpc/")
		  ("weblog" . "1"))
		 ))))
 
      ;; コマンドからの起動オプション用関数
      (defun kui-weblogger-edit ()
	"Start weblogger-mode"
	(interactive)
	(weblogger-select-configuration "default") ;; "default" 部分は適切な文字に置き換え
	(weblogger-fetch-entries))
 
      ;; コマンドからの起動オプション用関数
      (defun kui-weblogger-create ()
	"Start weblogger-mode"
	(interactive)
	(weblogger-select-configuration "default") ;; "default" 部分は適切な文字に置き換え
	(weblogger-start-entry))))

次に .zshrc に下記を追加

alias wordpress="emacs -f kui-weblogger-edit"
alias wordpress="emacs -f kui-weblogger-create"

これで、シェルから wordpress ってコマンド一発で WordPress を編集できる状態になる

問題点

  • タグが消える
  • 保存・公開した時間がおそらく世界標準時になってしまう
  • 新規作成周りがやっぱり不安定

ここまで書いておいてなんだけれど、ちょっとまだ実用的じゃないのかもしれない。

Cygwin + PuTTY + zsh + screen + emacs での日本語環境設定

この記事の目的は

  • cygwin を putty 経由で使う
  • シェル(zsh)、emacs、screen で日本語の表示、入力ができる

ということをすること。以下簡単に箇条書き。

  1. Cygwin をインストール
    • emacs, zsh, screen を install にする
  2. PuTTY ごった煮版をインストール
  3. Tera Term のインストーラーから cygterm をインストール
  4. putty で、cygterm 用の設定(セッション)を作成

    PuTTYを起動し、localhost:23にtelnet接続するセッションを1つ作成する。
    端末->行規則オプション->ローカルエコー を”強制的にオフ”
    端末->行規則オプション->ローカルラインの編集 を”強制的にオフ”
    端末->キーボード->バックスペースキー を”Control-H”に
    ウインドウ->変換 で文字コードを”MS_Kanji/Auto-Detect Japanese”に

    PuTTYを使ってCygwinに接続する – ばーぶろぐ /var/blog

    セッションの名前は「cygterm」にしておく。この名前は、次の cygterm.cfg の編集時に使う。

  5. cygterm の設定をする

    おそらく “C:\Program Files\teraterm” に cygterm.cfg というテキストファイルがあるので、適当なエディタで開く

    TERM = "C:\Program Files\PuTTY\putty.exe" -load "cygterm" -telnet %s -P %d
    TERM_TYPE = xterm
    PORT_START = 20000
    PORT_RANGE = 40
    SHELL = /bin/zsh --login -i
    ENV_1 = SHELL=/bin/zsh
    

    “TERM = …” の行の cygterm は、前で設定した PuTTY の Cygwin 用セッション名

  6. Windows の環境変数 Path に、”C:\cygwin\bin” を追加

    マイコンピュータ → 右クリック → プロパティ → 詳細設定タブ → 環境変数 → システム環境変数 Path を編集

  7. cygtem.exe を実行
    「cygwin1.dllが見つかりません」 ってエラーがでたら、先程の環境変数 Path に、”C:\cygwin\bin” があるか確認。もしくは cygwin インストールしたフォルダが、”C:\cygwin” じゃない?
    また、別の問題だが、関連して、Windows Vista/7 では zsh が起動時にエラーを吐く。対処方法は、PuTTYを使ってCygwinに接続する – ばーぶろぐ /var/blog を参考にさせていただいた。
  8. 自宅サーバから、.zshrc, .screenrc, .emacs をダウンロード
  9. .zshrc を編集
    下記のように LANG の値を変更、なければ追加

    export LANG=ja_JP.SJIS
  10. .screenrc を編集
    下記のように defencoding の値を変更、もしくは追加

    defencoding SJIS
  11. .emacs を編集
    下記の三つの値を設定、もしくは値を変更

    (set-language-environment "Japanese")
    (set-terminal-coding-system 'sjis)
    (set-keyboard-coding-system 'sjis)

おわり。日本語表示入力に関してはこれで問題ない。

参考にさせていただいたページ

lucene の demo で Could not find the main class: org.apache.lucene.demo.IndexFiles.

Luceneを使いたくて、最新のJDKと最新のAntをインストールし、Luceneをコンパイルして、Apache Lucene – Building and Installing the Basic Demo を参考に demo を動かしてみようとした。、上記のエラーが出た。

$ java -version
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8) (6b18-1.8-0ubuntu1)
OpenJDK Client VM (build 14.0-b16, mixed mode, sharing)
 
$ ant -version
Apache Ant version 1.8.1 compiled on April 30 2010
 
$ java org.apache.lucene.demo.IndexFiles src
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/lucene/demo/IndexFiles
Caused by: java.lang.ClassNotFoundException: org.apache.lucene.demo.IndexFiles
        at java.net.URLClassLoader$1.run(URLClassLoader.java:217)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:334)
Could not find the main class: org.apache.lucene.demo.IndexFiles. Program will exit.

原因は、Luceneをantでビルドしたときに warning が出ていたところあたりにある様子。

lucene-3.0.1のディレクトリにある build.xml を書き換えないといけない。build.xml の中の javac タグに属性includeAntRuntimeを書き加える。(220行目付近に一箇所)

      <javac
        srcdir="@{srcdir}"
        destdir="@{destdir}"
        deprecation="off"
        includeAntRuntime="true" ← ここだよ!!!
        debug="on"
        source="${javac.source}"
        target="${javac.target}">
        <nested/>
      </javac>

これで動くはず。

しかし実際はダメでした。これは別に原因があった。僕は、CLASSPATH に lucene-core-3.0.1.jar と lucene-demos-3.0.1.jar を置いてなかった為、下記のようなコマンドで demo を実行する必要があった。

$ java -cp /home/kui/lucene/lucene-3.0.1/lucene-core-3.0.1.jar:/home/kui/lucene/lucene-3.0.1/lucene-demos-3.0.1.jar  org.apache.lucene.demo.IndexFiles src

これで無事 lucene の demo が動きましたとさ。

ちなみに、ダメな時の ant のエラーは下記のようでした。

    [javac] /home/k-ui/lucene/lucene-3.0.1/build.xml:225: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds

Java で HelloWorld さえしてない自分には大変な作業でした。半日使ってしまった悲しい。

MacOSX の Firefox で、拡張機能 LoL(HaH) の「新しいタブで開く」問題

マウスなしでもリンクを辿れる拡張機能 LoL(元祖はHit-a-Hint) にはとてもお世話になってます。
Macbook で Firefox の LoL(HaH, Hit-a-Hint) を使っていると、「新しいタブで開く」って動作が正しく行われない問題に関する対処方法について書きたいと思う。

問題:mac Firefox にて LoL, HaH の「新しいタブで開く」が正しく動作しない

Win Firefox の LoL だと、新しいタブでリンクを開くには、

  1. スペースキーを押す(押したまま)
  2. 目的のリンクにあてられたキーを押す
  3. Ctrl キーを押しつつ、スペースキーを放す

とやれば良い。が、どういうわけか、Mac Firefox の LoL で同様のことをしようと

  1. スペースキーを押す(押したまま)
  2. 目的のリンクにあてられたキーを押す
  3. command キーを押しつつ、スペースキーを放す

ってやっても、リンクにフォーカスがあたるだけでリンクを開いてくれない。
仕方が無いので、最後に「command+return を押す」をして、新しいタブを開いている。
これが結構ストレス。

何かいい方法ないかなーってグーグル先生に教えて貰った。(ソースを紛失)どうやら、command キーを押すと、スペースキーの開放を検知出来ない仕様らしい。ブラウザレベルか、OSレベルかはわかりませんが。

解決方法:アドオン書き換え

ちょっと面倒ですが、LoL を書き換えましょう。command キーではなく control キーで新しいタブを開くように改造します。

手順1:LoL をダウンロード

まず LoL ダウンロードページで、右クリックして「別名でリンク先を保存」してください。

手順2:xpi ファイルと jar ファイルの展開

ダウンロードしたファイルは zip で圧縮されているので展開します。

$ unzip lol-1.4-fx.xpi 
Archive:  lol-1.4-fx.xpi
   creating: chrome/
  inflating: chrome/LoL.jar          
  inflating: chrome.manifest         
   creating: defaults/
   creating: defaults/preferences/
  inflating: defaults/preferences/prefs.js  
  inflating: install.rdf

さらに、LoL.jar も zip で圧縮されているのでやっぱり展開します。

$ cd chrome 
$ unzip LoL.jar 
Archive:  LoL.jar
 extracting: content/bindings.xml    
 extracting: content/settings.css    
 extracting: content/overlay.xul     
 extracting: content/preferences.xul  
 extracting: content/overlay.js      
 extracting: locale/fr-FR/hah.dtd    
 extracting: locale/it-IT/hah.dtd    
 extracting: locale/en-US/hah.dtd    
 extracting: locale/fi-FI/hah.dtd    
 extracting: locale/hu-HU/hah.dtd    
 extracting: skin/icon.png

手順3:overlay.js 書き換え

書き換えます。僕は Emacs なので Emacs で。

$ emacs content/overlay.js

そして、「initmouse」で検索してください。四つくらい見つかるかな?そして

                                  evt.initMouseEvent('mousedown', true, true, view, 1, x+1, y+1, 0, 0,
                                                                         event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, 0, null);
                                  elem.dispatchEvent(evt);
 
                                  var evt = doc.createEvent('MouseEvents');
                                  evt.initMouseEvent('click', true, true, view, 1, x+1, y+1, 0, 0,
                                                                         event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, 0, null);

と initMouseEvent が二回使われていますね。そこの第13引数に書いてある「metaKey」を「ctrlKey」に置換します。
こうなる。

                                 var evt = doc.createEvent('MouseEvents');
                                  evt.initMouseEvent('mousedown', true, true, view, 1, x+1, y+1, 0, 0,
                                                                         event.ctrlKey, event.altKey, event.shiftKey, event.ctrlKey, 0, null);
 
elem.dispatchEvent(evt);                                                                                 
 
                                  var evt = doc.createEvent('MouseEvents');
                                  evt.initMouseEvent('click', true, true, view, 1, x+1, y+1, 0, 0,
                                                                         event.ctrlKey, event.altKey, event.shiftKey, event.ctrlKey, 0, null);
                                  elem.dispatchEvent(evt);

手順4:元の通りに圧縮する

元の通りに圧縮しましょう。何となく勿体無いので、元の xpi ファイルは取っておき、mylol.xpi というファイルを作っています。

$ zip -r LoL.jar content locale skin 
  adding: content/ (stored 0%)
  adding: content/bindings.xml (deflated 78%)
  adding: content/overlay.js (deflated 75%)
  adding: content/overlay.js~ (deflated 75%)
  adding: content/overlay.xul (deflated 65%)
  adding: content/preferences.xul (deflated 74%)
  adding: content/settings.css (deflated 35%)
  adding: locale/ (stored 0%)
  adding: locale/en-US/ (stored 0%)
  adding: locale/en-US/hah.dtd (deflated 54%)
  adding: locale/fi-FI/ (stored 0%)
  adding: locale/fi-FI/hah.dtd (deflated 55%)
  adding: locale/fr-FR/ (stored 0%)
  adding: locale/fr-FR/hah.dtd (deflated 55%)
  adding: locale/hu-HU/ (stored 0%)
  adding: locale/hu-HU/hah.dtd (deflated 54%)
  adding: locale/it-IT/ (stored 0%)
  adding: locale/it-IT/hah.dtd (deflated 54%)
  adding: skin/ (stored 0%)
  adding: skin/icon.png (deflated 0%)
$ cd ..
$ zip -r mylol.xpi chrome/LoL.jar chrome.manifest defaults install.rdf
  adding: chrome/LoL.jar (deflated 23%)
  adding: chrome.manifest (deflated 63%)
  adding: defaults/ (stored 0%)
  adding: defaults/preferences/ (stored 0%)
  adding: defaults/preferences/prefs.js (deflated 60%)
  adding: install.rdf (deflated 63%)

手順5:インストールする

出来上がった mylol.xpi を、Firefox のウィンドウにドラッグアンドドロップして下さい。
おしまい。
こうすることで、

  1. スペースキーを押す(押したまま)
  2. 目的のリンクにあてられたキーを押す
  3. control キーを押しつつ、スペースキーを放す

とすると、新しいタブを開くことが可能になります。以上おしまい。

Emacs キーバインドに関する Windows と Mac OSX の差異

Mac OS X が手元に来て7ヶ月くらい経とうとしてる。そろそろ軽度(!?)Emacs 病を患っている人間から見た Windows と Mac OS X の違いをまとめてみた。

具体的には、Emacs に関わる話としては、

  • Emacs っぽいエディターについて(?)
  • 普段の GUI 操作において、Emacs キーバインドがどのくらい通用するか(?)

という話があります。

しかし?って他探せば幾らでも記事あるだろうから、今回は言及しません。。。というかよくわかりません。普段からローカル環境で開発せず、研究室の Linux サーバや、自宅サーバで開発をしているので、ローカル環境でエディター使わない。

今回は?の話を書いて行こうと思います。少し分かりにくいけど、Windows の話を読めば何となく分かるはず。C-p でフォーカスを上に移動させたり、C-m が改行だったり、C-Space がマークセットだったり、M-v で1ページ分スクロールできたり、といった操作を、webブラウザや、ファイラなどでも使うことです。

Windows

まずそもそも標準では全くサポートされていません。?を実現させる為には、 窓使いの憂鬱Xkeymacsなどのソフトウェアが必要になります。以下、Windows 上での Emacs キーバインド環境の特徴。

  • サポートしている Emacs キーバインド多い!マークセットできるのは windows だけ(?)。C-x から始まる2ストロークキーバインドに対応してるのも特徴。
  • アプリケーションによって、emacsキーバインドを有効にさせるか否かを設定することが可能。ちょうべんり
  • 時々入力キーをフックし損ねて大変なことに(例:C-nでスクロールしようと連打 → フックし損ねまくる → 新規ウィンドウ大量生成 → はんのうがない ただの はこ のようだ)
  • 時々挙動へん。Xkeymacs に限った話ですが、例えば「C-m と入力すると、Ctrl+Return の入力になってる。」とか、「『Shift+Tab』と入力しようと S-C-i とタイプする→Shift キーがロックされて解除できなくなる」など
  • MS Office と相性悪過ぎ。ごくごくタマに Office 落ちる。
  • MS IMEと相性悪い。(ので、MS IME の時は無効化し、MS IME 自体のキーバイン変更機能を利用)

実は、VISTA になってからの状況がよくわかってません。とりあえず Xkeymacs は、VISTA 上で問題なく使えました。

Mac OS X

私が知っている限りだと、二通り存在します。

  • 標準のemacsキーバインド(ただしテキストエリア限定)
  • KeyRemap4MacBook

標準の方も、素晴らしい点があるのですが、テキストエリア限定なのが残念。

KeyRemap4MacBook の特徴を下でまとめます。

  • 安定してる。フックし損ねた経験無し。
  • マークセットできない。。。(標準だと C-i で出来た記憶があるのですが。)
  • 特定ソフトウェアのときは、無効化するという機能がついて入るのですが、無効化できるソフトウェアが予め決まっている。ソースコード書き換えれば可能なようです。iTerm 使えない。
  • 日本語入力システムに対する Emacs キーバインドは、入力対象のソフトウェアに依存してしまう(例:Terminal.app の時だけ Emacs キーバインドを無効化すると、Terminal.app への日本語入力時も無効化されてしまう)。凄く困ってる。

最後のは、OS の構造上仕方の無い問題なんでしょうかね。。。

ついでに Linux(GNOME)

GNOME の標準機能(要設定)keyfake の二通り。

GNOME の標準機能は、Mac OS X の標準機能と似た感じ。テキストエリア限定。

keyfake は。。。うごかにあ!フツーにコンパイルしちゃダメなのかな。

まとめ

Windows、Mac OS X、Linux(GNOME) における Emacs キーバインドの対応状況を確認しました。

Windows
不安定だけど多機能、マークセットできる
Mac OS X
安定してる。日本語入力システム、他アプリケーションとの兼ね合いがうまくいってない
Linux(GNOME)
が、がんばれ!しかし皮肉ですね。。。

ということで、Windows が一番好きです。

しかし。。。

xkeymacs, 窓使いの憂鬱、KeyRemap4MacBook、keyfake のいずれも日本人が開発してる。。。?HENTAI!HENTAI!HENTAI!

Ruby でパイプラインな HTTP リクエスト

HTTP/1.1 の同時接続数について – daily dayflower をみて「おお、こんな事出来たのか。pipelining すげぇ」と思い、Ruby でこれやるのにはどうしたらいいのか 404 Blog Not Found:HTTPサーバーのパイプライン対応 を参考に考えてみました。

どうやら、Net::HTTP は、HTTP プロトコルのパイプライン処理をできるようになってないようす。その為、Net::HTTP の拡張が必要になりました。

メインコード

404 Blog Not Found:HTTPサーバーのパイプライン対応 のパイプラインの例を ruby コード化するとこんな感じ。

require "net/http"
require "pipelinig_http.rb" # Net::HTTP の拡張
 
def main
 
  host = 'b.hatena.ne.jp'
  paths = ['/','/hotentry', '/news', '/entrylist?sort=hot']
 
  Net::HTTP.start(host) do |http|
 
    # リクエストの送信
    paths.each do |path|
      req = Net::HTTP::Head.new(path)
      r_print req
      http.just_request(req)
    end
 
    # レスポンスの取得
    while res = http.fetch_response
      r_print res
    end
 
  end
 
end
 
# リクエストとレスポンスのヘッダを出力する
def r_print(r)
  puts "##### #{r.class.to_s} #####"
    r.each{|k,v| puts "%15s: %s" % [k,v]}
  puts ""
end
 
main if __FILE__ == $0

上記のコードで、require されてる pipelining_http.rb は、今回のために用意したファイルで、次に示す内容になっています。

パイプライン処理のための Net::HTTP の拡張 pipelining_http.rb

Net::HTTP#just_request, Net::HTTP#fetch_response は、次のように拡張される。実質、Net::HTTP#request を二つに分断した感じなってます。一応アップロードしておく。また今回の例では HEAD リクエストしかしていませんが、GETもPUTもできるはずです。(PUTは未確認)

pipelining_http-0.1.tar.gz

class Net::HTTP
 
  def just_request(req, body=nil)
 
    @req_cue ||= []
    @req_cue << req
 
    unless started?
      start {
        req['connection'] ||= 'close'
        return request(req, body, &block)
      }
    end
    if proxy_user()
      unless use_ssl?
        req.proxy_basic_auth proxy_user(), proxy_pass()
      end
    end
 
    req.set_body_internal body
    begin_transport(req)
 
    req.exec(@socket, @curr_http_version, edit_path(req.path))
 
  end
 
  def fetch_response
 
    if @req_cue.nil? or @req_cue.empty?
      end_transport(@last_request, @last_response)
      return nil
    end
 
    @last_request = (req = @req_cue.shift)
 
    begin
      @last_response = (res = Net::HTTPResponse.read_new(@socket))
    end while res.kind_of?(Net::HTTPContinue)
 
    res.reading_body(@socket, req.response_body_permitted?) {
      yield res if block_given?
    }
 
    res
  end
 
end

個人的に、end_transport のあたりの処理が気に入りません。要改善。

実行結果

##### Net::HTTP::Head #####
         accept: */*
 
##### Net::HTTP::Head #####
         accept: */*
 
##### Net::HTTP::Head #####
         accept: */*
 
##### Net::HTTP::Head #####
         accept: */*
 
##### Net::HTTPOK #####
           vary: Accept-Encoding
            via: 1.0 squid.hatena.ne.jp:3128 (squid/2.6.STABLE14)
   content-type: text/html; charset=utf-8
           date: Sat, 25 Oct 2008 20:01:48 GMT
         server: Apache/2.2.3 (CentOS)
     set-cookie: b=$1$edWQVbYY$4h0bIVtR7RvBTX3ifNcOF1; path=/; expires=Fri, 20-Oct-28 20:01:48 GMT; domain=.hatena.ne.jp
 content-length: 63501
            age: 588
    x-framework: Hatena/2.1
 
##### Net::HTTPOK #####
           vary: Accept-Encoding
            via: 1.0 squid.hatena.ne.jp:3128 (squid/2.6.STABLE14)
   content-type: text/html; charset=utf-8
           date: Sat, 25 Oct 2008 20:01:48 GMT
         server: Apache/2.2.3 (CentOS)
     set-cookie: b=$1$qYZ0MfS0$aCDFrIOKpp8HayJftFjt/1; path=/; expires=Fri, 20-Oct-28 20:01:48 GMT; domain=.hatena.ne.jp
 content-length: 103301
            age: 1163
    x-framework: Hatena/2.1
 
##### Net::HTTPOK #####
           vary: Accept-Encoding
            via: 1.0 squid.hatena.ne.jp:3128 (squid/2.6.STABLE14)
   content-type: text/html; charset=utf-8
           date: Sat, 25 Oct 2008 20:01:48 GMT
         server: Apache/2.2.3 (CentOS)
     set-cookie: b=$1$00vjav3o$InTi7h2XFjwllpcFJLnv60; path=/; expires=Fri, 20-Oct-28 20:01:48 GMT; domain=.hatena.ne.jp
 content-length: 52390
            age: 2259
    x-framework: Hatena/2.1
 
##### Net::HTTPOK #####
           vary: Accept-Encoding
            via: 1.0 squid.hatena.ne.jp:3128 (squid/2.6.STABLE14)
   content-type: text/html; charset=utf-8
           date: Sat, 25 Oct 2008 20:01:48 GMT
         server: Apache/2.2.3 (CentOS)
     set-cookie: b=$1$IZcB88yy$I2jjsa5SWRPLEBT4saLNI.; path=/; expires=Fri, 20-Oct-28 20:01:48 GMT; domain=.hatena.ne.jp
 content-length: 103620
            age: 2203
    x-framework: Hatena/2.1

以上でした。

Fedora 9 に mpg123 をインストール

Fedora 9 に mpg123 入れるのに,

$ sudo yum install mpg123

でいけると思ってた...結果は

Loaded plugins: refresh-packagekit
Warning: No matches found for: mpg123
No Matches found
$ sudo yum search 123
Loaded plugins: refresh-packagekit
================================= Matched: 123 =================================
flac123.i386 : Command-line program for playing FLAC audio files
pastebin.noarch : A collaborative debugging tool
vorbisgain.i386 : Adds tags to Ogg Vorbis files to adjust the volume

どうやら yum には頼れないようです. ってか flac123 があって mpg123 が無いのって何なんだろう...納得いかない.

ということで,RPM Search で見つけた mpg123 の RPM パッケージをインストールすることをする.mpg123 をインストールするに当たって,mpg123 が必要としているパッケージもインストールする.

$ sudo yum install arts portaudio
$ sudo rpm -i 'ftp://ftp.pbone.net/mirror/atrpms.net/f9-i386/atrpms/stable/mpg123-1.5.1-8.fc9.i386.rpm'
$ mpg123 'http://www.nullsleep.com/mp3/8bp077-01-nullsleep-her_lazer_light_eyes.mp3'

....鳴らない...?

と思ったら音量が最小だったという罠.適切な音量を設定して再トライしたところ,問題なく動きました!ってか Gnome 使うと,ALSA から音量のコントロールできないのね.どうしたらいいんだろ.

last.fm のラジオ再生のための Ruby ライブラリを作ってみた

以前のブログの記事で、Google Code 上の thelastripper にて公開されているドキュメント (LastFM12UnofficialDocumentation) の簡単な説明と、スクリプトを公開しました。

Linux を NAS 兼メディアセンターとして使っているので、Linux で X 立ち上げずに last.fm ラジオの再生できたらなぁと思ってたりしてました。

ということで、LastFM12UnofficialDocumentationを元に、last.fm ラジオの再生の助けになるライブラリを作ってみました。

lastfmradio-0.1.tar.gz のダウンロード

本当は、RubyForge なり GitHub になり置いた方が良いかもしれないんですが、どっちがいいんですかねぇ。

同梱されているドキュメント。。英語ですがかなり怪しいですね。

使い方

メソッドごとに説明をするよりも、サンプルコードの方がどんな感じかわかりやすいと思うので、コードで二つほど例示します。
基本的に、ハンドシェイク → ラジオ局の選択 → トラックの取得 → 処理 の流れは変わりません。省かれているように見える場合は、内部で処理をしています。

一番基本的な例

mpg123 を使ってラジオ再生する

#! ruby -Ku
 
# last.fm 上でのアカウント名とパスワード
user_name = 'USER_NAME'
password = 'PASSWORD'
 
# 内部ではハンドシェイクをしている
lastfm = LastfmRadio.new(user_name, password)
 
# electoronic タグラジオに合わせる
lastfm.adjust_station('tag', 'electoronic')
 
loop do
 
  # トラック情報の取得
  # クラス Track のインスタンスの Array を返す
  tracks = lastfm.get_tracks
 
  tracks.each do |track|
 
    # トラック情報の表示
    puts "-"*75
    puts "artist: #{track.artist}"
    puts "title:  #{track.title}"
    puts "album:  #{track.album}"
    puts "length: #{track.length/1000}[sec]" 
 
    # mpg123 を使って現在のトラック再生
    `mpg123 -q #{track.location}`
  end
 
end

LastfmRadio#get_tracks メソッドは要注意。返してくれるトラックの数はまちまちです。ラジオ局によっては、 1 つも取得できない時があります。これは、引数に整数を与えることで解決ができます。たとえば引数に 2 をあたえると、2 個以上のトラックを取得するまでリターンしなくなります。

LastfmRadio#adjust_station メソッドは、第一引数に以下のようなシンボル or 文字列をとります。

tag または globaltags
第二引数で指定されるタグラジオを選択
artist
第二引数で指定されるアーティストに似たラジオを選択
personal
第二引数で指定されるアカウントのパーソナルラジオを選択
recommended
第二引数で指定されるアカウントのおすすめラジオを選択
neighbours
第二引数で指定されるアカウントのお隣さんのラジオを選択(日本語。。。)
group
第二引数で指定されるグループのラジオを選択

一番簡単な例

wget を使ってラジオから流れてくる曲をダウンロード

require "lastfmradio"
 
hash = {
  :user_name => "SOME_USER",
  :password => "PASSWORD",
  :station => ["tag","electoro"], # adjust_station の引数と同じ
  :min_num_tracks => 2,  # 2 個以上のトラックを必ず取得(optional)
  #:debug => true # デバグモード(optional)
}
 
loop do
 
  # ハンドシェイク、局の選択、トラックの取得をする
  # 二回め以降は、同じユーザ名・局の場合は、ハンドシェイク、局の選択はしない。
  tracks = LastfmRadio.get_tracks(hash)
 
  tracks.each do |track|
 
    puts "-"*25 + " " + Time.now.to_s + " " + "-"*25
    puts "artist: #{track.artist}"
    puts "title:  #{track.title}"
    puts "album:  #{track.album}"
    puts "length: #{track.length/1000}[sec]"
 
    `mpg123 -q #{track.location}`
  end
end

こんな感じで使えます。

しかしここまで書いたら、アプリケーションとして公開した方がよかったかな。。。気にしない。。。。ruby ライブラリを公開したの初めてですが、インストーラーとかみんなどうしてるんでしょうかね。