Haskellでテンキーに動的なキーバインドを設定できるやつを作った

テンキーに対して動的なキーバインドを設定するためのHaskellライブラリ"WildBind"をリリースした。

現状はX11デスクトップ環境のみサポートしている。要はxbindkeysみたいなものだが、その実体は普通のHaskellモジュールである。以下のようにして使う。

import WildBind.Task.X11
import System.Process (spawnCommand)

main = wildNumPad myBinding

myBinding = binds $ do
  on NumCenter `run` putStrLn "Hello, world!"
  on NumPageUp `run` spawnCommand "firefox"

「動的なキーバインド」というのは、ここでは2通りの意味で使っている。

第1に、WildBindではアクティブなウィンドウに応じてキーバインドを変化させることができる。例えば、Firefoxが開いているとき、動画プレーヤー(VLCとか)が開いているとき、PDFビューワ(evinceとか)が開いているとき、それぞれに対してキーバインドを設定すれば、それらが自動的に切り替わる。

第2に、WildBindではキーバインド自体が状態を持ち、自身の状態によってキーバインドを変化させることができる。そのため、例えばEmacsのようにキーシーケンスに対してアクションを割り当てることができる。

なお、現状ではWildBindはテンキー上のキーにのみ、キーバインドを設定できる。これは、全てのキーを対象とするように作るのが面倒だったのと、全てのキーを対象とした場合は動的なキーバインドなどほとんど必要ないと思ったからである。テンキーという限られたスペースに可能な限り機能を詰め込むのがWildBindの基本的な考え方である。

詳しい説明やコード例はGitHub上のREADMEを参照されたい。



ところで余談だが、WildBindは数年前に作ったNumpaarというものが元になっている。

NumpaarはPerlとCで書かれていて、やたら複雑なマルチプロセス構成になっており、使いづらい上に不安定だった。そこで今回、全てをHaskellで書き直すことにしたのだった。

慢性上咽頭炎の近況 (5)

前回の近況から3ヶ月が経過した。これまであったことを記す。

幸い、あれから症状が極端に悪化することはなく、耳鼻科に行くこともなく過ごせている。

ただ、依然としてたまに以下の症状が現れている。

  • 頭がぼうっと熱い感覚。午前中からお昼にかけてが多い。
  • 軽いめまい。
  • 疲労感。特に夜に感じ、眠くなる。鼻の奥がツーンとする感覚や、軽い頭痛を伴うことがある。

6月下旬と8月下旬、それぞれ1〜2週間ほどこうした症状が現れていた。

症状が出るきっかけというか、原因についてはよく分からない。睡眠不足、気候の変化、あるいは心理的ストレスだろうか。ただ、症状が出ているときはかなり早く寝てもすぐにはよくならなかったように思う。

とはいえ、逆に言えば上記の期間以外はほとんど症状を感じなかったので、ずいぶん良くなったほうだと思う。

ロリポップ!のサーバ移設でInternal Server Errorが出たので対応

ブログやQiitaに書かないようなこと(最近は主にUbuntuのインストールメモ)は個人的に立てているPukiWiki(http://debugitos.main.jp/)の方に書くようにしているが、昨日ここにアクセスしたら"Internal Server Error"が出てしまっていた。

このPukiWikiロリポップ!のサービスでホストしていただいているのだが、そういえば8/18に「サーバ移設」が行われていた。

ということで、多分このせいでエラーが出るようになったのだろう。

いろいろ調べたところ、どうもアクセス制限のための設置していた.htaccessがいけなかったらしい。もともとは<Files>ディレクティブを使って一部のファイルにのみアクセス制限をかけていたが、これを取り除くと動くようになった。

サーバ移設によってApache httpdバージョンが2.2から2.4に上がったらしいが、そのせいでエラーになっていたのか、それとも利用可能なディレクティブがより少なく設定されているのか、よく分からない。

ただ、この作業の過程でよかれと思ってPHPバージョンを5.2から5.6に上げたのだが、そのせいで今度はPukiWikiのバグによるエラーが出るようになった。

どうやら古いバージョンのPukiWikiではPHP 5.4以降でエラーが出るらしい。

ということで、せっかくなのでPukiWikiを最新版に差し替えて、ついでにEUC-JPからUTF8に移行することにした。

まず、phpをローカルにインストールする。コマンドラインで起動できれば十分。

$ sudo aptitude install php-cli php-mbstring

次に、以下のサイトからdata2utf8.php.txtをダウンロードし、data2utf8.phpに名前変更。

data2utf8.phpPukiWikiのルートディレクトリに設置して、実行。

$ php -f data2utf8.php

これで文字コードの変換は完了。

UTF-8の最新版PukiWikiに旧PukiWikiのデータを流し込む。

$ cp -an ../old/wiki/* wiki/
$ cp -an ../old/attach/* attach/

backupやcacheは面倒なのでいじらなかったが、これでなんとなくうまく動くようになった。

あとは適宜skinを移植。基本的にimageリソースとskin/pukiwiki.{css,skin}.phpをコピーすればそのまま動いた。

ただ、これでサイト上のPukiWikiページにアクセスするとどうもページロードが遅い。というか、スタイルがワンテンポ遅れて当たっている感じだ。調べてみると、pukiwiki.css.phpのロードに700msもかかっていた。遅すぎる。

ということで、pukiwiki.css.phpが吐いたCSSを手動でファイルに保存し、skinではこのCSSファイルを直接読みこむように変更したところ、だいぶマトモなスピードで動作するようになった。

慢性上咽頭炎の近況(4)

前回の近況からまた2ヶ月がたったので、これまであったことを記す。

Bスポットの終了

2016年4月5日にいつも通り耳鼻科でBスポット療法を受けた。この日でだいたいBスポットを始めてから半年が経過したことになる。症状はだいぶ改善してきているし、ここでいったん定期的なBスポット療法をやめ、様子を見ることになった。

耳鼻科の先生が言うには、「何ヶ月かしてまた戻ってくる人もいれば、こない人もいる。ともかく鼻呼吸を心がけよ」とのことだった。

それからここ2ヶ月の間は以下のような症状があった。

  • 後鼻漏。
  • 頭がぼうっとする感覚。目まいほどではないが、午前中に間食すると起こりやすかった。
  • 光がまぶしい感覚。特に午後3時から4時あたりで西日が強い時によく感じた。
  • 軽い吐き気。頻度は高くなかったが、液晶モニタでずっと文書を読んでいる時などに感じた。

こういった症状はコーヒーを飲んだり涼しい場所に移動することでやわらげることができた。

いずれにせよ、これらの症状は次第に弱くなってきており、今ではほとんど感じない程度になっている。

その他の措置

鼻うがいは朝夕に1日2回、引き続き行っている。特に寝ている間に鼻の奥に鼻水がたまることがあるので、朝に鼻うがいをすると気分がスッキリする。

さすがに鼻うがいには慣れてきたようで、水を押し込むタイプでも吸い込むタイプでもほぼ百発百中で鼻からノドに流せるようになってきた。不慣れなうちは、特に押し込むタイプではノドに抜けなかったことがあったが、ノドに抜ける感覚をイメージしながらゆっくり水を押し流すとうまくいくように思う。

腕と頭へのお灸は、最近は頻度を少し減らしてみている。結局効果があったのかどうか、よく分からない。鍼灸院で買ったもぐさがまだだいぶ余っているので、それを使い切るまでは続けようかと思っている。

.cabalファイルのghc-optionsに何を書くべきか

ふと、Haskellパッケージの.cabalファイルのghc-optionsフィールドに何を書いておくとよいか不安になったので、少し調べた。

なお、cabalが実際にどのようなオプションをghcに与えているかは--verboseオプションで確認できる。

$ cabal build --verbose

stackを使う場合、--verboseオプションはstack自身の、--cabal-verboseオプションはcabalのverbosityを制御する。

$ stack build --verbose --cabal-verbose

参考:

-Wall -fno-warn-unused-imports

これらは書くようにしている。

"-Wall"はご存知コンパイラの警告を全て表示するオプションである。

"-fno-warn-unused-imports"は不必要なモジュールやシンボルのimportに対する警告を無効化するものである。古いバージョンのghcでは必要だったimportが新しいバージョンのghcでは必要無くなることがあるのだが、このオプションがないとそんな場合にも警告が出てうっとうしいからだ。

-O[n]

最適化オプション。個人的には、これは書かなくてよいと思っている。

cabalはデフォルトで-Oオプションをghcに渡してコンパイルするらしい(cabal-install-1.24.0.0時点)。この挙動はconfigure時に設定できる。

$ cabal configure -O2

デフォルトでレベル1の最適化をするので、まあ特に設定しなくていいかなと思っている。他人のパッケージの.cabalをいくつか見てみたが、-O2と書いているものもあれば、何も書いてないものもあり、まちまちなようだ。

他のオプションでもそうだが、最終的にはユーザーがビルドする際に上書き設定すればよいだろう。

-X[LANGUAGE_EXTENSION]

ghc拡張機能スイッチ。これは書くべきではない。.cabalではdefault-extensionsフィールドとother-extensionsフィールドが使える(Cabal-1.10から。それより前ならextensionsフィールドが使える)ので、こっちを使うべきである。

なお、default-extensionsフィールドに書いた拡張機能はパッケージ内の全モジュールで有効になる。other-extensionsフィールドはパッケージが使う拡張機能を宣言するだけのものであり、各モジュールはLANGUAGEプラグマを使って必要な拡張機能のみを有効にする。

-threaded

OSスレッドを使用する。リンカオプションなのでlibraryセクションでは(多分)不要。他は個別の事情に合わせて、といったところだろう。

プログラムの作りによっては-threadedオプションがないと動かなかったり、逆にあると動かなかったりするので注意を要する。

以前、同じテストプログラムを-threadedのあるバージョンとないバージョンで2通りテストしたいことがあったが、結局test-suiteセクションをコピーして2つ並べてしまったことがある。何かよい方法はないだろうか。

-rtsopts

コマンドラインやGHCRTS環境変数からのRTSオプションを全て受け付ける。リンカオプションである。入れたほうがexecutableを使う立場としては便利だが、セキュリティ上のリスクがあるのかもしれない。

なお、デフォルト(-rtsopts=some)ではごくごく一部のRTSオプションしか受け付けないらしい。

"-with-rtsopts=-N[n]"

"-with-rtsopts"はRTSオプションをリンク時に指定する。-Nオプションはn個のOSスレッドの同時使用を許可する。n省略時はCPUコア数に応じたよしなな値が使われる。-threadedをつけるならセットでつけておいた方がなにかと便利だろう。

なお、-with-rtsoptsによる設定は-rtsoptsがなくても有効になる。-rtsoptsはあくまでプログラム実行時にRTSオプションを設定できるかどうかを制御するものである。

また、-with-rtsoptsではスペース区切りで複数のRTSオプションを並べることができるが、これを.cabalに書くには上に示したように-with-rtsopts=...全体をダブルクォートするべしとのこと( https://github.com/haskell/cabal/pull/1346 )。

"-with-rtsopts=-M512m"

"-M"オプションは最大ヒープサイズを制限する。自分の書いたプログラムのバグによってメモリが無限に食いつぶされるのが怖いので、自分はtest-suiteにはこれをいれるようにした。(参考: http://qiita.com/debug-ito/items/87fa50d5324e20936d7e )

Stackageに自分のパッケージを上げる

最近はもうstackとstackageには頼りっきりで、使いたいパッケージがstackageに入っていないとちょっとイラッとくるくらいになってきたが、そういえば自分もいくつかのパッケージをhackageに上げている。大したものではないとはいえ、それらをstackageに載せないのはスジが通らないなと思い、stackageに上げることにした。

さて、自分のパッケージをstackageに上げるにはどうすればいいか。必要なことは以下のページに書いてある。

手順としては、stackageレポジトリをforkして、build-constraints.yamlに自分の名前とパッケージのリストを追加して、pull requestを送るだけだ。

ただし、stackageに入れるパッケージは以下の3点を満たす必要がある(あるいは、満たす「べき」である?)

  1. All packages are buildable and testable from Hackage.
  2. All packages are compatible with the newest versions of all dependencies.
  3. All packages in a snapshot are compatible with the versions of libraries that ship with the GHC used in the snapshot

travisスクリプトとビルドエラー対応

第1の条件はこれまで通りきちんとパッケージングしてhackageに上げれば満たされる。

テストに関しては.travis.ymlが提供されているのでこれをサクっと利用するのがいいだろう。

GHC + cabalおよびstackを用いたビルドパターンをいくつかのバージョンでやっている。なかなか良さそうだ。

なお、stackageに上げたパッケージがビルドに失敗する場合、「1週間以内を目安に解決しろ」とのこと。


依存パッケージのバージョン上限問題

第2の条件は、stackageに上げるパッケージが依存するパッケージのバージョン上限に関係する。

例えば、.cabalファイルに以下のようなbuild-dependsがある場合、

  build-depends: foo-bar >=0.3.0.1 && <0.5

foo-bar-0.5がリリースされると第2の条件が満たされなくなる。この場合、foo-barに依存しているパッケージの作者が(1週間以内を目安に)解決しなければいけない。foo-barの変化が小さければ新バージョンでのビルドとテストを確認してbuild-dependsを変更するだけでよい。そうでない場合はちょっと厄介なことになるだろう。

ここで問題となるのは、依存パッケージが他人のものである場合、それがいつメジャーバージョンアップされるか分からないということだ。この問題に対処するために、以下のサービスが公開されている。

このサービスは検索したパッケージの依存パッケージのバージョン上限を調べ、それが現在の最新版をカバーしているかチェックしてくれる。パッケージ検索ではauthorフィールドも検索対象になるので、例えば筆者の場合は

をウォッチすればいい。RSSフィードも吐いてくれるので、こいつをフィードリーダーに突っ込んでおけば速やかに依存パッケージのメジャーバージョンアップに対処できる、はずだ。

なお、PVPには反してしまうが、依存パッケージにバージョン上限を記載しないという考え方もある。この件についてMichael Snoyman氏がブログに書いている。

stackage的にはバージョン上限はあってもなくても構わない、ということだろうか。また、stack sdistとstack uploadには--pvp-boundsというオプションがあり、build-dependsのバージョン下限・上限をいい感じに書き加えてくれるらしい。

個人的には、今のところbuild-dependsのバージョンは自分で管理すればいいと思っている。いろいろ規模が大きくなると破綻しそうな気もするが。


依存パッケージのバージョン下限問題

第3の条件は依存パッケージのバージョン下限に関係する。要は、「GHCにバンドルされるパッケージバージョンがカバーされるくらいには下限を広く取れ」ということである。

このことの理由は以下で詳細に説明されている。

"lenient"とは"not as strict as expected"という意味らしい。

例えばあるGHCバージョンにパッケージfoo-bar-0.2がバンドルされており、自分のパッケージのbuild-dependsがfoo-bar >=0.3だとする。すると、バンドルされたfoo-bar-0.2とユーザスペースにインストールされたfoo-bar-0.3が混在する状況になりうる。これが結構ややこしいコンパイルエラーを引き起こし、運が悪い場合は詰む。

さらに言うと、Haskell Platformで提供されるパッケージバージョンをカバーしておくといいようだ。これらは以下のページで確認できる。

ぶっちゃけそれほど多くないので、現状では人力でチェックすればいいと思う。

Gtk-Uguiss-2.00をリリースした

Gtk2/Gtk3/xfwm4テーマであるGtk-Uguissの新バージョンを作った。

バージョン1.xx系列はXubuntu 14.04 (Trusty)が、バージョン2.xx系列はXubuntu 16.04 (Xenial)がターゲットである。いろいろ試行錯誤して目に着いたバグは潰したのでリリースした。

なお、そもそもなんでGtkのテーマなぞ作っているかはGtk2/Gtk3/xfwm4テーマを作った - DebugIto’s diaryを参照。

バージョン2.00の主な変更点は以下の2点である。

  • Xfce4.12の新ウィンドウスイッチャへの対応。
  • Gtk3の怒濤の変化への対応。

Xfce tabwin

TrutyのXfceは4.10だったが、Xenialでは4.12になっている。Xfce4.12では、ウィンドウスイッチャ(通称"tabwin")が新しくなり、ウィンドウサムネイルの表示やマウスでのウィンドウ選択が可能になった。

ということで、このtabwin向けの色設定を追加した。

Gtk3の互換性問題

Gtk3は、Trusyではバージョン3.10, Xenialでは3.18になっている。この間、互換性を破壊するものを含む様々な変更が加えられたようで、その対応に追われた。新しく追加されたウィジェットやエフェクトに正常にスタイルが当たらないならともかく、checkboxやspinnerといったごく基本的なウィジェットへのスタイル設定がぶっ壊れているのを見た時には開いた口が塞がらなかった。

Gtk3の連中は後方互換性を維持するつもりがあるのだろうか。あまり好き勝手なことをやっているとユーザ(ここではテーマ開発者)から愛想をつかされ、コミュニティは衰退し、結局自分たちしか使えない何かになり下がりはしないか。実際、Xubuntu 16.04のリリースブログ記事にはこんな文言がある。

Albatross, Bluebird and Orion GTK+ themes have been dropped since they do not support newer GTK3 versions

16.04になってデフォルトのテーマ設定の選択肢が非常に狭まったと感じたが、Gtk3のせいなのだろう。

もちろん、新機能を追加したり既存の問題を解決したりするためには後方互換性を破壊する変更がしばしば必要になるので、なかなか難しいところではある。Gtk3の開発ロードマップがどうなっているのか知らないが、どこかの時点でメンテナンスモードになって、非互換な開発はGtk4で進むようになることを願う。

Gtk3のテーマ開発

Gtk3のテーマ設定は大部分がCSSで書かれているが、自分は以下のような手順で修正を行っている。

  1. gtk3-demo、gtk3-widget-factory、その他のGtk3アプリを使って問題のある箇所を見つける。
  2. その箇所に設定されていそうなCSSクラス名を推測し、テーマファイル(ほとんどの場合はgtk-widgets.css)内を探す。
  3. 修正すべき箇所を同定するため、CSSファイルを書き換えて「マーカースタイル」を設定する。例えば、background-color: redとかborder: solid 3px redとかpadding: 50pxとか、とにかく変化が目立つもの。
  4. マーカースタイルが狙い通りの場所に現れていればそこが修正すべき場所なので、適宜修正する。マーカースタイルが表れていなければ、別の場所を試す。

このように、いわばprintfデバッグCSS版のようなことをやっている。実に効率が悪い。

Web開発であればCSSを修正する作業はもう少し楽なはずだ。ドキュメントの構造はHTMLを見れば分かるし、最近のWebブラウザには超優秀なインスペクタがデフォルトで積んであるので、どこに書かれたスタイルがどう効いているのか一目瞭然である。ブラウザ上でリアルタイムに修正することすらできる。

Gtkにそういうものはないのか?

と思って調べたら、まさにGtkInspectorというものがあるそうだ。

Gtk3.14から利用可能とのこと。gtkparasiteというツールが土台となっているらしい。なるほど、これは具合が良さそうだ。今度なにかあったら試してみよう。もっと早く調べるべきだった。

XfceとGtk3

tabwinのスタイル設定をして気付いたのだが、tabwinを含むXfce4.12のアプリはいまだ大部分がGtk2を使っている。テーマ開発者としては安定しているGtk2を使ってくれる方が助かるが、いつまでもGtk2を使っていていいのかという不安もある。

と思っていたら、開発者のブログにちょうどそういった記事が上がっていた。

どうやらXfce4.14の目標は、大部分のアプリをGtk3にポートすることらしい。ほっとしたような気もするし、余計なことを・・ ともちょっと思う。

まあXfceならそんなにキレッキレのGtk3機能は使わず、無難なウィジェットにおさまることだろう。