Pythonで書き直す

結構前にWolfram|Alphaの携帯クライアントアプリを書いていたのですが、サイト側のリニューアルでデータが正しく取れなくなってそのまま放置していました。
最近必要かなと思い始めてきたのでサーバ側のコードを書き換えて不完全ながら動くようにしました。

最初に開発したときにはAPIが有料だったのでサイトのHTMLをごにょごにょしていましたが
今では月2000回まで無料になったのでAPIキーを取得して書き直すことに。

その結果225行あったコードが100行ぐらいになりました。
APIで楽できた分を差し引いてもだいぶ短くなりました。
どうもSchemeで身に着けた関数型の書き方が遺憾なく発揮されている模様です。
今までほとんど使うことのなかったmapが大活躍したり、
処理を細かい単位に分割するようになったりで可読性も結構あがったような気がします。

↓短くなるところ(前)

# start threading
threads = getasyncpods(pod_nodes, pod_list, target_server)
recalculate_re = re.compile("recalculate.jsp\?[^'\"]*")
recalc_jsps = recalculate_re.findall(outputhtml)
# Out[73]: ['recalculate.jsp?id=MSP(...)a7bd540&asynchronous=pod&i=e&s=23']
def done(recalchtml):
	pod_list = findpodjsp(recalchtml)
	done.threads += getasyncpods(pod_nodes, pod_list, target_server)
		
done.threads = threads
for jsp in recalc_jsps:
	w = Worker(target_server + jsp, callback=done)
	w.start()
	threads.append(w)
		
# end threading
for t in threads:
	t.join()
pods = [poddetail(pod, imgflg, threads) for pod in pod_nodes]
# end threading for pod images
for t in threads:
	t.join()
pods = [pod for pod in pods if pod is not None]

これが4行に収まった。

threads = [Worker(self.pod, (po,)) for po in tree.getElementsByTagName('pod')]
map(threading.Thread.start, threads)
map(threading.Thread.join, threads)
pods = [t.result for t in threads]

Fireboot3正式版

だいぶまえの話になりますがFireboot 3.0の正式版を公開しました。

http://aoproj.web.fc2.com/fireboot/

最適化後に拡張機能の対応バージョンが下がってしまうが色々してると直るというバグがありますが致命的ではないのでおそらくすぐには直さないかと思います。

今回素敵な背景画をリク氏に描いてもらいました。本当にありがとうございます。

    • -

ずっとまえから画面を華やかにする何かがほしいと思っていて、初代Firebootでは自分でPhotoshopを使って加工した画像を背景に、ボタンなどのUI類を絵の邪魔にならないように(絵を中心に据えるように)配置して、普通のプログラムとはちょっと違う感を演出していました。
これまで画面デザインに関するコメントは見たことがなかったのでこれがどんな印象を与えるものだったのかは謎のままですが、個人的に満足ではありました。

二代目FirebootはFlex3.0を使っているため見た目がやたらスタイリッシュになりました。内部のアルゴリズムも大幅に変更し設定ファイルを見て消すべき言語を自動検出する設計にしました。今思うと抜けが多くて初代より性能が落ちているような気がしなくもないですが。
エラー報告機能によって問題のありそうなアドオンの名前とエラーになった場所がわかるようになりました。2.0の開発環境を失った(Flex Builderのライセンスキーを紛失)
今では残念ながらほとんど役立っていない機能です。
サポートを終了したいまでも時々報告ボタンが押されているのを見ると正直ないほうがよかったかもしれない・・・と思わされます。
プライバシーポリシーを策定していなかったのも反省。

三代目はSchemeでちょこちょこ書き上げました。
やっていることは初代と一緒でファイル名だけで削除するかどうかを判定しています。簡単こそ至高。機能的には今までで一番ボリュームがあるバージョンになりました。
悪くない出来だと思いますので、縁があれば使ってみてください。

http://aoproj.web.fc2.com/fireboot/

SchemeでCSS圧縮

字句解析器silexでCSSを圧縮できるのか。
言語ファイルの削除だけでは限界が見えてきたFireboot。
しかしほかのファイルを機能を維持したまま圧縮できればよりサイズが抑えられるはず。
そんなわけで手始めに一番簡単そうなCSSの圧縮に挑戦してみました。

完成品

字句の定義はほぼ仕様書通りなのでわりと簡単にできました。

; css.l - CSS3 Tokenizer
; SeeAlso: http://www.w3.org/TR/2003/WD-css3-syntax-20030813/
nonascii    [\128-\55295\57344-\65533\65536-\1114111]
nl          \10|\13\10|\13|\12
wc          \9|\10|\12|\13|\32
w           {wc}*
unicode     "\\"[0-9a-fA-F]{1,6}{wc}?
escape      {unicode}|"\\"[\32-\126\128-\55295\57344-\65533\65536-\1114111]
nmstart     [a-zA-Z]|"_"|{nonascii}|{escape}
nmchar      [a-zA-Z0-9]|"-"|"_"|{nonascii}|{escape}
name        {nmchar}+
ident       "-"?{nmstart}{nmchar}*
urlchar     [\9\33\35-\38\39-\126]|{nonascii}|{escape}
stringchar  {urlchar}|\32|"\\"{nl}
string      "\""({stringchar}|"'")*"\""|"'"({stringchar}|"\"")*"'"
num         [0-9]+|[0-9]*"."[0-9]+

%%

; The following productions are the complete list of tokens in CSS3:
{ident}     (make-tok 'IDENT yytext yyline yycolumn)
"@"{ident}  (make-tok 'ATKEYWORD yytext yyline yycolumn)
{string}    (make-tok 'STRING yytext yyline yycolumn)
"#"{name}   (make-tok 'HASH yytext yyline yycolumn)
{num}       (make-tok 'NUMBER yytext yyline yycolumn)
{num}"%"    (make-tok 'PERCENTAGE yytext yyline yycolumn)
{num}{ident}    (make-tok 'DIMENSION yytext yyline yycolumn)
"url("{w}({string}|{urlchar}*){w}")"    (make-tok 'URI yytext yyline yycolumn)
"U+"[0-9A-F?]{1,6}("-"[0-9A-F]{1,6})?   (make-tok 'UNICODE-RANGE yytext yyline yycolumn)
"<!--"      (make-tok 'CDO yytext yyline yycolumn)
"-->"       (make-tok 'CDC yytext yyline yycolumn)
{wc}+       (make-tok 'S yytext yyline yycolumn)
"/*"[^*]*"*"+([^/][^*]*"*"+)*"/"    (make-tok 'COMMENT yytext yyline yycolumn)
{ident}"("  (make-tok 'FUNCTION yytext yyline yycolumn)
"~="        (make-tok 'INCLUDES yytext yyline yycolumn)
"|="        (make-tok 'DASHMATCH yytext yyline yycolumn)
"^="        (make-tok 'PREFIXMATCH yytext yyline yycolumn)
"$="        (make-tok 'SUFFIXMATCH yytext yyline yycolumn)
"*="        (make-tok 'SUBSTRINGMATCH yytext yyline yycolumn)
[^"']       (make-tok 'CHAR yytext yyline yycolumn)
\65279      (make-tok 'BOM yytext yyline yycolumn)
<<EOF>>     (make-tok '*eoi* yytext yyline yycolumn)
<<ERROR>>   (make-tok 'ERROR yytext yyline yycolumn)

インタプリタで字句解析器を実際に生成して、それを利用してコメントや空白を適当に無視しながら新しいCSSを書き出します。
もっと込み入った最適化もできるとは思うけど、かかる時間を考えると割に合わないのでこれでおしまい。
ちょっと試してみるとサイズがだいたい2割ぐらい削れる感じ。
それでいて圧縮後も文法的に問題がでないようになっています。

(use silex) ;; 字句解析器生成器
(lex "css.l" "css.lex.scm" 'counters 'all)
(include "css.lex.scm")
(define lexer-error error)
(define (in? sym list)
	(if (null? list)
		#f
		(or (eq? sym (car list))
			(in? sym (cdr list)))))
(define (minify-css port-string-proc)
	(define port (open-output-string))
	(define result port-string-proc)
	;(if (port? port-string-proc)
	;	(lexer-init 'port port-string-proc)
	;	(if (string? port-string-proc)
			(lexer-init 'string port-string-proc)
	;		(if (procedure? port-string-proc)
	;			(lexer-init 'procedure port-string-proc)
	;			(error 'minify-css "input is not any of port, string or procedure."))))
	(let loop ((token (lexer)) (prev (make-tok 0 0 0 0)))
		;(print token)
		(case (get-tok-type token)
			((S COMMENT)
				(write-char #\space port)
				(loop (lexer) prev)) ; コメントスキップ
			#;((IDENT ATKEYWORD NUMBER PERCENTAGE DIMENSION
			URI UNICODE-RANGE FUNCTION)
				(if (or (in? (get-tok-type prev)
							'(IDENT ATKEYWORD HASH NUMBER DIMENSION))
						(equal? (get-tok-lexeme prev) "]"))
					(write-char #\space port))
				(write-string (get-tok-lexeme token) #f port)
				(loop (lexer) token)
			)
			((ERROR) (msg "CSS文法エラー")(close-output-port port))
			((*eoi*) (set! result (get-output-string port))
					(close-output-port port)) ; 終了
			(else ; どうでもいいもの(Stringとか)
				(write-string (get-tok-lexeme token) #f port)
				(loop (lexer) token)
			)
		)
		result
	)
)
;prev×token いけない組み合わせの一覧
;IDENT×IDENT
;		ATKEYWORD
;		NUMBER
;		PERCENTAGE
;		DIMENSION
;		URI
;		UNICODE-RANGE
;		FUNCTION
;ATKEWWORD×同上
;HASH×同上
;NUMBER×同上
;DIMENSION×同上
;PERSENTAGE×なし
;STRING×なし
; bookmarked: http://jigsaw.w3.org/css-validator/validator
(minify-css "some.css {\n string:\n 100%;\ra :'aaa'\n} b{bdr: 100% 10% 10% 1em;}")
(minify-css "div.dlsite_circle_profile img {\n    border: 0 none;\n    border: 1px solid #CCCCCC;\n    margin: 5px;\n    vertical-align: bottom;\n}\n.advTitle, .advUrl {\n    text-decoration: underline !important;\n}\n.side ul.recent-article-image li:after {\n    clear: both;\n    content: \".\";\n    display: block;\n    height: 0;\n    visibility: hidden;\n    background: url(\"http://parts.blog.livedoor.jp/img/plugin/cms_link/icon_write.gif\") no-repeat scroll left top transparent;\n}")
(define stest (read-string #f (open-input-file "highlighter.css")))
(string-length stest)
(string-length (minify-css stest))

ちび電卓作ってみた

Chicken Scheme構文解析はできるのか検証しました。
自分でパーサ書くのは死ぬほどしんどいというのはJavaで体験済みなのでライブラリを探してみます。

アーッ yacc無かった!

yaccがあれば楽になると期待してみたところ残念ながらありませんでした。
あれはあるのにこれは無いというeggライブラリのこの微妙な充実度。Zipのライブラリならともかくさすがにパーサジェネレータを書く気にはなれません。
・・・と思ったところで発見!しかもYaccと同じLALR(1)の構文解析器。よかった!
後もうひとつ、字句解析器が必要です。Cでいうyylex()あたり。
字句解析のほうは死ぬほどまで大変ではないので手書きでも大丈夫・・・
ところでrubyはどうやってるのか?
Rubyソースコード完全解説の第10章あたりにyylex()の解説があります。

yylex()はとても長い。現在1000行以上ある。そのほとんどが巨大な switch文一つで占められており、文字ごとに分岐するようになっている。まず一部省略して全体構造を示す。

・・・/(^o^)\
大丈夫とか絶対嘘ですよ、これ。

というわけで

今回は構文解析と字句解析に lalr と silex の2つのエッグを使用します。
chicken-install -k lalr silex
どっちのライブラリもドキュメントが不十分なのでコードを読む気でかかりましょう。
ちび電卓のファイルは3つに分割しました。

  1. 字句解析用の定義ファイル calc.l
  2. 構文解析用の定義と両解析器を生成する prepare.scm
  3. ちび電卓の本体 calc.scm
; calc.l - An example for SILex
; 2011 moondial0.net
; (yycontinue) yytext yyline yycolumn yyoffset
; See Also: http://api.call-cc.org/doc/silex

space   " "|" "
vblank  "\n"|"\r"|"\r\n"
digit   [0123456789]+
symbol  "+"|"-"|"*"|"/"|"="
letter  [a-zA-Z]+

%%

{digit} (cons 'NUMBER (string->number yytext))
{symbol} (string->symbol yytext)
{letter} (cons 'ID (string->symbol yytext))
{vblank} (list 'NEWLINE)
<<ERROR>> (make-tok <<ERROR>>-tok yytext yyline yycolumn)
<<EOF>> (print "---File Border Line---")'*eoi*
; -*- coding: sjis -*-
; prepare.scm
; 
(use silex) ;; 字句解析器生成器
(print "字句解析器生成中...")
(lex "calc.l" "calc.lex.scm" 'counters 'all) ;; 字句解析器生成
;;第四引数が'noneならyytextのみ 'lineならyytextとyyline
;;'allならyytext,yyline,yycolumn,yyoffsetが取得できる

(use lalr) ;; パーサ生成器 See Also: http://api.call-cc.org/doc/lalr
(print "構\文解析器生成中...")
;; 電卓用パーサを生成
(define calc-parser
	(lalr-parser
		;; *オプション*
		;; 同名のパーサをファイルに書き出す
		(output: calc-parser "calc.syntax.scm")
		;; *終端記号の定義*
		(ID NUMBER NEWLINE LPAREN RPAREN
			(nonassoc: =) ;; 非結合
			(left: + -) ;; 左結合
			(left: * /) ;; 上より優先順位が低い
			(nonassoc: -@) ;; 単項演算子のマイナス符号
		)
		;; *構文規則の定義*
		;(lines
		;	(lines line) : (print "継続行 " $2)
		;	(line) : (print "開始行 " $1)
		;)
		;(line
		;	(NEWLINE) : (print "空行")
		;	(assign NEWLINE) : (begin (print "宣言行 " $1) $1)
		;	(expr NEWLINE) : (begin (print "式の行" $1) $1)
		;	(error NEWLINE) : (begin (print "エラー行") #f)
		;; NEWLINEまでに構文エラーが起きたら発動
		;)
		(lines
			(lines NEWLINE line) : (begin(print "継続行")(append $1 (list $2)))
			(line) : (begin (print "開始行") (list $1))
			(error NEWLINE) : (print "エラー行")
		)
		(line
			(NEWLINE) : (begin (print "Exit!") (exit))
			(assign) : (begin (print "宣言 " $1) $1)
			(expr) : (begin (print "式 " $1) $1)
		)
		(assign (ID = expr) : (add-binding $1 $3)) ;; 代入
		(expr
			(NUMBER) : (begin (print "number: " $1) $1)
			(ID) : (get-binding $1)
			(expr + expr) : (+ $1 $3)
			(expr - expr) : (- $1 $3)
			(expr * expr) : (* $1 $3)
			(expr / expr) : (/ $1 $3)
			(- expr (prec: -@)) : (- $2)
			(LPAREN expr RPAREN) : $2
		)
	)
)
(print "コンパイル中...")
; -*- coding: sjis -*-
; calc.scm
;
(print "Hello, lexer!")
(use silex) ;; 字句解析器生成器
(include "calc.lex.scm")
(define lexer-error error)
(use lalr-driver) ;; 構文解析実行器 lr-driverの実装
(include "calc.syntax.scm")
;; 字句解析器
;(lexer-init 'string|port|procedure "")
;; 電卓用環境
(define *env* (list (cons '$$ 0)))
(define (add-binding var val)
  (set! *env* (cons (cons var val) *env*))
  val)
(define (get-binding var)
  (let ((p (assq var *env*)))
    (if p
        (cdr p)
        0)))
(define (init-bindings)
  (set-cdr! *env* '())
  (add-binding 'cos cos)
  (add-binding 'sin sin)
  (add-binding 'tan tan)
  (add-binding 'expt expt)
  (add-binding 'sqrt sqrt))
;; メイン
(define (parser-error . args)
	(print "ParseError: "args))
	;(display "parser error: ")(for-each display args)(newline))
(define (calc)
	(call-with-current-continuation (lambda (k)
		(print "ちび電卓 on チキンScheme")
		(init-bindings)
		;(lexer-init 'port (open-input-file "testfile"))
		(lexer-init 'port (current-input-port))
		;(lexer-init 'procedure read)
		(calc-parser lexer parser-error)
	))
)
(calc)

コンパイル・実行は

csc -v -extend prepare.scm calc.scm && calc

です。
開発中にlexerを(read)にして遊んだときの記録も残しておきます。

#;24> (calc-parser read parser-error)
(NUMBER . 10)
number: 10
+
(NUMBER . 20)
number: 20

*eoi*
ParseError: (Syntax error: unexpected end of input)
#f
#;25> (calc-parser read parser-error)
10
ParseError: (Syntax error: invalid token:  10)
#f
#;26> (calc-parser read parser-error)
(NUMBER . 10000)
number: 10000
+
(NUMBER . 20)
number: 20
NEWLINE
式の行10020
開始行 10020
*eoi*
#;27> (calc-parser read parser-error)
NEWLINE
空行?
開始行 #<unspecified>
*eoi*
#;28> (calc-parser read parser-error)
(NUMBER . 10)
number: 10
(ID . "a")
ParseError: (Syntax error: unexpected token :  (ID . a))
NEWLINE
エラー行
開始行 #f
*eoi*
#;29> (calc-parser read parser-error)
NEWLINE
空行?
開始行 #<unspecified>
NEWLINE
空行?
継続行 #<unspecified>
NEWLINE
空行?
継続行 #<unspecified>
*eoi*

(lexer-init 'port (current-input-port))
(lexer)
でlexerの動作も確認できました。
実際に動かしてみます。

C:\>csc -v -extend prepare.scm calc.scm&&calc
"C:\mingw\bin\chicken.exe" "calc.scm" -output-file "calc.c" -extend prepare.
scm
字句解析器生成中...
構文解析器生成中...
コンパイル中...
"gcc" "calc.c" -o "calc.o" -c  -fno-strict-aliasing -fwrapv -DHAVE_CHICKEN_C
ONFIG_H -DC_ENABLE_PTABLES -Os -I"C:/mingw/include/chicken"
rm calc.c
"gcc" "calc.o" -o "calc" -Wl,--enable-auto-import -L"C:/mingw/lib" -lchicken
 -lm -lws2_32
rm calc.o
Hello, lexer!
ちび電卓 on チキンScheme
10
number: 10
式 10
開始行
10+10
number: 10
number: 10
式 20
継続行
999999*99999/8
number: 999999
number: 99999
number: 8
式 12499862500.125
継続行
a=10000
number: 10000
宣言 10000
継続行
a*a*a
式 1000000000000.
継続行

Exit!

Fireboot 3.0β公開

久しく更新していなかったFirebootの最新β版をアップロードしました。
Firefox4.0以降に対応しています。詳しくは同梱のReadme.txtで。

aoproj.web.fc2.com/fireboot/Fireboot3beta.zip

正式版公開ページは気が向いたら用意します

実践 CHICKEN Scheme Qt eggをWindowsで動かす

Chicken Scheme 4.7.0のWindows環境でqt-light eggを普通にchicken-installすると(use qt-light)時にエラーが出てインポートに失敗します。
Qtの導入には地雷が多くて何度も躓いてしまったのでその対策をまとめました。
Ubuntuでは前回の記事の手順で問題ありません。
http://d.hatena.ne.jp/moondial0/20110603/1307100385

Qt4をChickenに導入する

すでにChickenやMinGWをインストールしている場合はいったん削除してください。これは、qt-light eggを動かすにはgccの特定バージョンが必要だからです。

まずQt4です。
http://qt.nokia.com/downloads-jp から
右側の「Windows 版 Qt ライブラリ 4.7.3 のダウンロード (minGW 4.4, 322 MB) 」と
左下 Qt Creator IDE の「Windows 版 Qt Creator 2.2 のダウンロード(52MB)」
をダウンロードします。
ここで「Qt SDK for Windows (15 MB) 」を選んでしまうと
約157000ファイル(約6.6GB)がインストールされます。なんという地雷。
QtSDKのインストール先はデフォルトのまま "C:\Qt\4.7.3"、MinGWのインストール位置は "C:\MinGW" とします。
Qt Creatorのインストール時には必ずMinGWのチェックを入れたままにしておきます。
インストール後にこの C:\Qt\qtcreator-2.2.0\mingw を C:\MinGW にコピーします。

環境変数Pathの末尾に「;C:\MinGW\bin\」を追加します。詳しいやり方は環境変数でググればわかります。

コマンドプロンプト(cmd.exe)を開き
mingw32-make -v
でバージョンが表示されたらきちんとPathが通っています。

CHICKEN Schemeリリース版をダウンロードしてきます。
http://code.call-cc.org/
Cドライブに解凍したフォルダをchickenと改名して置きます。

コマンドプロンプトから以下のコマンドを実行します。

C:\Users\moondial>mingw32-make -v
GNU Make 3.82
Built for i386-pc-mingw32
Copyright (C) 2010  Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org
This is free software: you are free to change and redistri
There is NO WARRANTY, to the extent permitted by law.

C:\Users\moondial>cd C:\chicken

C:\chicken>mingw32-make PLATFORM=mingw PREFIX=C:/mingw
...中略...
C:\chicken>mingw32-make PLATFORM=mingw PREFIX=c:/mingw install
...中略...
C:\chicken>csc -V
(c)2008-2010 The Chicken Team
(c)2000-2007 Felix L. Winkelmann
Version 4.7.0
windows-mingw32-x86 [ manyargs dload ptables ]
compiled 2011/05/06  on moondial (MinGW)

Enter `chicken -help' for information on how to use the compiler,
or try `csc' for a more convenient interface.

Run `csi' to start the interactive interpreter.

C:\chicken>set QTDIR=C:\Qt\4.7.3
C:\chicken>chicken-install -k qt-light ←-kオプションで一時ファイルを残す
retrieving ...
resolving alias `kitten-technologies' to: http://chicken.kitten-technologies.co
uk/henrietta.cgi
connecting to host "chicken.kitten-technologies.co.uk", port 80 ...
requesting "/henrietta.cgi?name=qt-light&mode=default" ...
reading response ...
HTTP/1.1 200 OK
...中略...
installing qt-light: ...
changing current directory to C:\DOCUME~1\moondial\LOCALS~1\Temp/temp1afe/qt-light
...中略...
C:\chicken>cd C:\DOCUME~1\moondial\LOCALS~1\Temp/temp1afe/qt-light ←qt-lightの一時フォルダへ移動
C:\DOCUME~1\moondial\LOCALS~1\Temp\temp1afe\qt-light>mingw32-make -f qt-light.make.Release clean
C:\略\qt-light>mingw32-make -f qt-light.make.Release
C:\略\qt-light>copy /Y "release\qt-light.dll" "qt-light.so"
C:\略\qt-light>C:\mingw\bin\csc -feature compiling-extension -setup-mode -s -O3 -d0 qt-light.import.scm
C:\略\qt-light>copy /Y "qt-light.so" "c:\mingw\lib\chicken\6\qt-light.so"
C:\略\qt-light>copy /Y "qt-light.import.so" "c:\mingw\lib\chicken\6\qt-light.import.so"

はまるポイント: qt-light eggはなぜかchicken-installしたときに壊れたファイルがインストールされるので一時フォルダに入ってコンパイルしなおします。
chicken-install時に表示されたコマンドを手で実行しているだけなのですが、なぜこれでうまくいくのかはまったくの謎です。環境変数あたりが悪さをしているのでしょうか。
また、qt-light.import.soも作り直さないとうまくいかないのでこれまたはまってしまいました。
最後にQtが使用するDLLをパスの通るフォルダにコピーします。実行形式を配布する時にはこれらのDLLを実行ファイルと同じフォルダに同梱しておくのを忘れないようにしましょう。

C:\略\qt-light>cd C:\Qt\4.7.3\bin
C:\Qt\4.7.3\bin>copy QtCore4.dll C:\MinGW\bin
C:\Qt\4.7.3\bin>copy QtGui4.dll C:\MinGW\bin
C:\Qt\4.7.3\bin>copy QtOpenGL4.dll C:\MinGW\bin
C:\Qt\4.7.3\bin>copy QtXml4.dll C:\MinGW\bin
C:\>csi
#;1> (use qt-light)
#;2> (qt:init)
#<qt-application>
#;3> (qt:message "hello, Qt4 world!")
0

変換キーをEnter替わりに使う

Ubuntuにも慣れてきたのでいろいろいじってみたくなった。
Win7で多用していたキーバインドをなんとかして使えないか?
探してみたら設定ファイルを書くだけで行ける模様。
というわけでここを参考に ~/.Xmodmap を作成した。
http://d.hatena.ne.jp/opamp_sando/20110704/1309792606
Xfce環境だとうまくいくかわからなかったけどやってみたらでけた。

keycode 98 = Katakana NoSymbol Katakana
keycode 99 = Hiragana NoSymbol Hiragana
keycode 100 = Return NoSymbol Henkan_Mode
keycode 101 = Hiragana_Katakana Romaji Hiragana_Katakana Romaji
keycode 102 = Muhenkan NoSymbol Muhenkan

やっぱこれだね!