gnome-shellのmigemo extension


先日Gnome3がリリースされた。ネットの評判を眺めてるとどうも芳しくはないようだが、個人的には気に入っていてすでにノートPCもデスクトップもGnome3になってしまっている。

もちろん不満が全く無いかといえばそんなことはなく、細かい不満はいくつもある。
そんな不満の中の1つがアプリの検索について。Gnome3ではSuperキーを押したときにキーボードからの入力でアプリの検索ができるが、アプリ名が日本語になっている場合などはいちいちかな入力をONにしてから入力をしなければいけなかったりと非常に面倒くさい。
そんなときはmigemoの出番。ということでアプリ検索でmigemoが使えるgnome-shellのextensionを作ってみた。

使い方

最初にmigemoに付属しているmigemo-clientが使える状態にするため、migemo-serverを起動させる。
自分の環境(Debian unstable)では.xprofileに

migemo-server /usr/share/migemo &

と記述してある。

次にextension本体を持ってくる。
https://github.com/papamitra/gnome-shell-migemo-extension
git cloneするか、上のページにあるDownloadsボタンをクリックしてDLする。

ディレクトリ構成はこうなる。

$ cd ~/.local/share/gnome-shell/extensions
$ tree migemosearch@papamitra.com
migemosearch@papamitra.com
|-- extension.js
`-- metadata.json

0 directories, 2 files

ディレクトリ名が気に入らない場合は名前を変えて、metadata.jsonのuuidを適切に変更する。

あとはgnome-shellを再起動すれば、extensionが有効になる。ログインしなおしてもいいけど、Alt-F2のダイアログでrコマンドを使うのが簡単。

使ってみるとこんな感じ。

最後にいくつか

コードを見てもらえば分かるとおり、かなり力技で実装してあるが一応自分の環境では実用的な速度で動作している。
Gjs(JavascriptGnomeバインディング)はドキュメントが少なくて非常に難儀した。
migemo-serverの起動とかもextension内で完結できないものか。

Compiler Pluginを作ってみた

なにげなくScalaコンパイラのソースを眺めていたときに見つけたコード

// src/compiler/scala/tools/nsc/util/trace.scala
object trace {
  def apply[T](msg: String)(value: T): T = {
    println(msg+value)
    value
  }
  // ...
}

なんてことないけど気の効いたコードだ。このコードいいな、とつぶやいたら

という返事があった。おぉlazy val、その発想はなかった!
どうせなら全てのlazy valにStackTrace出力を埋め込むちょっとウザいCompiler Pluginを作ってみよう思い立った。

ところでCompiler Pluginってなによ?

Compiler Pluginへの入門はこの記事が参考になると思う。
http://blog.takeda-soft.jp/blog/show/399

簡単に言うとコンパイルの各フェイズの間でちょっかいを出すコードが書けてしまうすごい機能。
(「lispのマクロに対するOdersky先生の回答だ」みたいなこと言ってた人もいたような気がする)
上の記事ではゼロ除算をコンパイル時に検出するPluginが解説されていたが、Compiler Pluginではコンパイル時に抽象構文木(AST)そのものをいじってしまうこともできる。
そういうPluginの例としてはNonnull-check generatorが紹介されていた。

作ってみた

とりあえず

lazy val hoge = new Hoge()

みたいなのを

lazy val hoge = { Thread.currentThread.getStackTrace foreach println; new Hoge()}

に変換するだけの簡単なCommpilerPluginを作ってみた。

唯一めんどくさそうなのが付加するコードをASTにするところだが、これは[twitter:@xuwei_k]さんが紹介してくれたMkTreeを使えば簡単に得られる。

余談

ここまで「Compiler Pluginって簡単に作れるよ」的な説明をしてきたけど、実際はここに辿りつくまでに大変時間がかかった。というのも何を間違えたかCompiler Pluginを挿し込む場所が、上のコードではparserフェイズの後になっているが、当初typerフェイズの後にしていたからだ。
せっかつくったのでそのコードも載せてみる。

先に挙げたコードとまったく同じ事をしているわけだが、やたら長い。
そしてAST作るのだけでも面倒なのに、さらに各Treeに手で型付けするという苦行(笑。
よっぽどのことがない限り

val runsAfter = List[String]("parser")

をおすすめします。

ScalaからJNAでCライブラリ(libpafe)にアクセス

常日頃名古屋鉄道名鉄)で通勤しているが、JRに遅れることはや数年、名古屋市営地下鉄とともについにIC乗車券(通称manaca)が導入された。
そして前回やっと解像度がまともに表示できるようになったと、喜びの報告をしたVAIO YにはPaSoRiICカードリーダ)が内蔵されている。せっかく内蔵されているのだからこれを使わない手はないということで、早速IC乗車券を購入してきた。

LinuxからPaSoRiにアクセスするにはlibpafeが利用できる。当初Pythonのctypesからlibpafeにアクセスしていたが、せっかく(?)なのでScalaから制御してやろうと思い立った。
結論からいうと知識不足のためなかなか苦戦したがなんとかlibpafeを叩くことができた。

ScalaからCライブラリへのアクセスする際の参考にもなると思うので、備忘がてらここに記載しておく。

libpafeの準備

libpafeのサイトからDLして./configure;make;make install。

作者さんの環境がDebianらしくdebパッケージ対応されているので、Debian系OSであれば

$ dpkg-buildpackage -rfakeroot

これだけでdebパッケージができる。便利。

あとはREADMEに従ってudevの設定をしてやればlibpafeの準備は完了。

JNAでlibpafeにアクセス

Scalaの標準ライブラリにはCライブラリへのアクセスなどは提供されていないので、Javaの資産を活用する。
JavaからCライブラリへのアクセスというとJNIが有名(?)らしいけど、今回はより簡単にアクセスできると評判のJNAを使ってみた。

とりあえずREPLでいけるところまでやってみる。まずはライブラリのインスタンスを取得。

$ scala -classpath /usr/share/java/jna-3.2.4.jar

scala> import com.sun.jna._
import com.sun.jna._

scala> val libpafe = NativeLibrary.getInstance("/usr/lib64/libpafe.so")
libpafe: com.sun.jna.NativeLibrary = Native Library </usr/lib64/libpafe.so@140237998485072>

次にPaSoRiにアクセスして初期化処理をおこなう。

scala> val pasori_open = libpafe.getFunction("pasori_open")
pasori_open: com.sun.jna.Function = native function pasori_open(/usr/lib64/libpafe)@0x7f8bbb0daba0

scala> val pasori = pasori_open.invokePointer(null)
pasori: com.sun.jna.Pointer = native@0x26aed70

scala> val pasori_init = libpafe.getFunction("pasori_init")            
pasori_init: com.sun.jna.Function = native function pasori_init(/usr/lib64/libpafe)@0x7fe027dfdd50

scala> pasori_init.invokeInt(Array(pasori))                
res0: Int = 0

libpafeの関数pasori_openを取得して実行している。引数は必要ないのでnullを渡している。invokePointerは返り値がPointerの関数実行。
Pasoriへのポインタを取得したのちpasori_initで初期化している。pasori_initの返り値0は正常終了。

ここまででPaSoRiを使う準備はできた。試しにPaSoRiのバージョンを取得してみる。

scala> import java.nio.IntBuffer
import java.nio.IntBuffer

scala>  val v1 = IntBuffer.allocate(1)                       
v1: java.nio.IntBuffer = java.nio.HeapIntBuffer[pos=0 lim=1 cap=1]

scala>  val v2 = IntBuffer.allocate(1)
v2: java.nio.IntBuffer = java.nio.HeapIntBuffer[pos=0 lim=1 cap=1]

scala> pasori_version.invokeInt(Array(pasori, v1, v2))
res11: Int = 0

scala> (v1.get, v2.get)                               
res12: (Int, Int) = (1,30)

参照渡しする場合はそれ用のオブジェクトをつくってやる必要がある。pasori_versionはintを参照渡しするのでIntBufferを使った。
1.30というバージョンが取得できた。

最後にPaSoRiをcloseする。

scala> val pasori_close = libpafe.getFunction("pasori_close")       
pasori_close: com.sun.jna.Function = native function pasori_close(/usr/lib64/libpafe)@0x7fe027dfdb50

scala> pasori_close.invokeVoid(Array(pasori))

JNAeratorで構造体を自動生成

簡単な関数であればREPLでも実行できてしまうあたりはさすがJNA。(というかScalaもすごい)しかし構造体を渡したり受け取ったりとなるとさすがに簡単にはいかない。具体的にはcom.sun.jna.Structureを継承して必要な構造体を作る必要がある。
幸いJNAeratorというものがある。これはヘッダから、Structureを継承したJavaクラスやその他ライブラリへのアクセスを簡単にするクラスなどをひとまとめにしたJarを作成してくれるすぐれもの。
しかし残念ながら生成されたJarを使用するとJVMがSIGSEGVしてしまうという現象にみまわれあえなく断念。JarではなくJavaのソースファイルを吐かせて、それを修正して使うことにした。

Javaソースを吐かせるには-noComp -noJarを付けて実行してやれば良い。

$ java -jar ./jnaerator-0.9.5.jar -noComp -noJar -o ./ -runtime JNA /usr/include/libpafe/libpafe.h -package org.libpafe

上のコマンドラインだとJNAランタイムを使用する(JNAeratorランタイムを使用しない)、パッケージがorg.libpafeのJavaソースをカレントディレクトリに吐き出す。

たとえば

struct tag_felica
{
  pasori *p;
  uint16 systemcode;
  uint8 IDm[8];
  uint8 PMm[8];
  uint16 area_num;
  felica_area area[256];
  uint16 service_num;
  felica_area service[256];
  struct tag_felica *next;
};

こういう構造体だと、以下のようなソースを吐く。

public class tag_felica extends Structure {
    /// C type : pasori*
    public org.libpafe.tag_pasori.ByReference p;
    public short systemcode;
    /// C type : uint8[8]
    public byte[] IDm = new byte[(8)];
    /// C type : uint8[8]
    public byte[] PMm = new byte[(8)];
    public short area_num;
    /// C type : felica_area[256]
    public org.libpafe.felica_area[] area = new org.libpafe.felica_area[(256)];
    public short service_num;
    /// C type : felica_area[256]
    public org.libpafe.felica_area[] service = new org.libpafe.felica_area[(256)];
    /// C type : tag_felica*
    public tag_felica.ByReference next;
    public tag_felica() {
    super();
    }
    // 以下略
}

ただし吐き出されたこのままを使用するとSIGSEGVになってしまう。それを回避するためにtag_pasori.ByReferenceやtag_felica.ByReferenceをcom.sun.jna.Pointerに書き換えて使っている。
ちなみにPointerから構造体へキャストするには、ポインタを引数にして構造体をnewしてやればよい。

最後に

まだ中途半端だけどSuica(manacaSuicaと同じフォーマット)の履歴を取得するコードをgithubに置いておく。
http://github.com/papamitra/scala-pasori

履歴を取るところまではできたので、Play! with Scalaと組み合わせて家計簿とかできないかな、などと画策中…

Debian+VAIO Yでようやく画面表示がまともになった

去年VAIO Y(VPCY2)を買いました。
さっそくDebian(squeeze)を入れたもののKMSが有効だと起動時に画面がブラックアウトしてしまうという症状に遭遇、やむなくKMSを無効にして*1Xのドライバもvesaにしてどうにか使用していました。

新しいカーネルがリリースされるたび試すも一向に症状は改善しなかったのですが、ついに2.6.38-rc2にて症状が改善、1366x768のくっきりした画面での表示が可能となりました。

無線LANが使用不可

2.6.38-rc2で画面表示は改善したものの無線LANが使用不可になってしまいました。
NetworkManagerでは「無線を有効にする」がグレイアウトして選択できない状態です。コマンドラインから有効にすることもできませんでした。

$ sudo ifconfig wlan0 up
SIOCSIFFLAGS: 未知のエラー 132

Google先生に問い合せた結果、無線デバイスの有効無効を制御するrfkillというツールで使用可能になることが判明しました。

$ sudo rfkill unblock all
$ sudo ifconfig wlan0 up

これで無線LANが使用可能になりました。
どうも起動時に無線LANバイスがブロックされた状態になっており、それを解除することによって使用できるようにしているらしいのですが、そもそもなぜブロックされてしまうのかといったあたりは全く不明です。

あと、毎回コマンド打つのはめんどいので、上のコマンドの内容を/etc/init.d/rfkill_unblock_allというファイルに保存して

$ sudo update-rc.d rfkill_unblock_all defaults 99

としてやれば毎回起動時にwlan0を有効にしてくれます。

残る課題

いまだ解決できないのはサスペンドについてです。サスペンドから復帰しても画面がブラックアウトしたままの状態になってしまいます。カーネルオプションでacpi_sleep=s3_biosを指定などしても効果はありませんでした。求む情報。

*1:/etc/default/grubGRUB_CMDLINE_LINUXにi915.modeset=0を追加してupdate-grubを実行