読者です 読者をやめる 読者になる 読者になる

mintty でピクセル単位のフォントサイズを指定する

フォントサイズ10.5pt(96dpi環境上での14px)が使いたいのに指定できないとお困りのあなたへ。

.minttyrc

FontHeight=-14

のようにマイナスで値を指定するとピクセル指定になります。

mintty のソースコード (wintext.c) にも

font_height =
  size > 0 ? -MulDiv(size, GetDeviceCaps(dc, LOGPIXELSY), 72) : size;

と書かれており、ちゃんとした挙動なのでごあんしんください。

ただし、ピクセル直接指定なので DPI Aware ではなくなります。

PostgreSQL の NOTIFY/LISTEN を使用してテーブルの変化を PHP から検知してみた

PostgreSQL 9.0 から NOTIFY/LISTEN という Pub/Sub を行う仕組みが入っていたので、試してみた。

NOTIFY/LISTEN の挙動を確認する

psql を3枚立ち上げる。

NOTIFY/LISTEN

LISTEN <channel>;<channel> の購読を開始する。

NOTIFY <channel>;<channel> を購読しているセッションに配信を行う。

購読を psql 上で待つときは、; [Enter] など、何かしら実行させることで通知が表示される。

トランザクションで囲んだ場合は

トランザクション1

のように、コミット時に配信される。

トランザクション内で同じチャンネルに対して複数回配信を行った場合は

トランザクション2

のように、複数回配信されることはなく、一度だけ配信される。

詳しくはマニュアルに記載されている。

リアルタイム配信システムを作ってみる

チャットのように、発言テーブルに INSERT されたら画面上にリアルタイムに発言が表示されるシステムを作ってみる。

テーブル定義

CREATE TABLE timeline (
  id serial NOT NULL PRIMARY KEY,
  body text NOT NULL,
  created timestamp NOT NULL DEFAULT now()
);

シーケンス、内容、時刻を持つだけの単純なテーブルを作った。 このテーブルに INSERT されたときに自動的に NOTIFY してほしいので、トリガーを作成する。

CREATE OR REPLACE FUNCTION notify_trigger() RETURNS trigger AS $$
  BEGIN
    PERFORM pg_notify(TG_ARGV[0], NULL);
    RETURN NULL;
  END;
$$ LANGUAGE 'plpgsql';

CREATE TRIGGER timeline_update AFTER INSERT ON timeline EXECUTE PROCEDURE notify_trigger('timeline_update');

pg_notify 関数は、NOTIFY 文が関数になったもので、チャンネル名やペイロード(通知の際に使用できる任意の文字列)が不定である場合に便利とされている。 トリガー関数の戻り値が NULL であるが、これはトリガーが AFTER INSERT であり、戻り値が利用されないため、何でも良い。

なお、トリガーに UPDATEDELETE も指定し、通知のペイロードとして変更のあった id を渡すことで、リアルタイムに変更を監視することも可能なはずである。

(追記) tcnモジュールを利用することでわざわざトリガー関数を作らなくても良いようだ。ただしこのモジュールは行指向のため、今回の用途からすると少しオーバースペックではある。

API

PHP 5.6 で作った。 なお、ここで使用している PDO::pgsqlGetNotifyPHP 5.6 以降で使用可能な関数であるため、 それ以前のバージョンを使うときは PostgreSQL モジュールに用意されている pg_get_notify を使用することになる。 (ただし、pg_get_notifyタイムアウトが指定できず、通知が無い場合は即座に FALSE が返るため、PHP 内でループを回すことになる… ダサい)

WebSocket などのモダンな技術で作っても良いのだが、単純化するために古典的な Long polling スタイルで作った。

ソースコードは少し長いので Gist にアップロードした。

function retrieve($since_id = 0, $limit = 10) {
    global $db;

    $stmt = $db->prepare("SELECT * FROM timeline WHERE id > :since_id ORDER BY id DESC LIMIT :limit");
    $stmt->bindValue(':since_id', $since_id);
    $stmt->bindValue(':limit', $limit);
    $stmt->execute();

    return $stmt->fetchAll();
}


$timeline = retrieve($since_id);

if (empty($timeline)) {
    $db->exec('LISTEN timeline_update');
    $result = $db->pgsqlGetNotify(PDO::FETCH_ASSOC, 30000);
    if ($result === false) {
        output();
    }
    $timeline = retrieve($since_id);
}

Twitter 風に、since_id を指定できるようにした。 API 呼び出し時点で idsince_id より上の発言があればそれを返し、無ければ発言されるまで(INSERT されるまで)待機し、新規発言を返す。 30秒たっても新規発言がなければ、空配列が返る。

retrieve.php?since_id=<max_id> をブラウザで開くと読み込み中のままになり、psql などから timeline テーブルに追加すると即座にブラウザ上にレスポンス (JSON) が表示される。 since_id を用いた取得なので、INSERT INTO timeline (body) VALUES ('foo'), ('bar') のように2行が一度に INSERT されても、ちゃんと2行とも表示される。

フロントエンド

モダンなフレームワークを使うべきだが今回は jQuery

$(function() {
    var retrieve_and_display = (function() {
        var current_id = 0;
        return function() {
            $.post('retrieve.php', { since_id: current_id })
            .done(function(result) {
                if (result.length > 0) {
                    current_id = result[0].id;
                    result.reverse();
                    $.each(result, function() {
                        $('<li />').text(this.body).prependTo($('#timeline'));
                    });
                }

                retrieve_and_display();
            });
        };
    })();

    retrieve_and_display();
});
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
    <script src="//code.jquery.com/jquery-2.1.3.min.js" type="text/javascript"></script>
    <script src="pushtest.js" type="text/javascript"></script>
    <title></title>
</head>
<body>

<p>timeline:</p>

<ul id="timeline">
</ul>

</body>
</html>

retrieve_and_display 関数を呼び出すたびに未取得の発言を最大10件取得し、#timeline の先頭に追加する。 サンプルなのでいろいろツッコミどころがあるが(ajax request が fail したときの処理がない、jQuery で DOM を生成するな、そもそも jQuery を使うな etc.)、そこはサンプルということでひとつ。

ブラウザで開き、最新の10件が表示され、psql 等で timeline テーブルに追加すれば即座にブラウザ側にも反映されることが確認できた。

まとめ

PostgreSQL の NOTIFY/LISTEN を使用することで、Pub/Sub を実現できた。 Publish 側は PostgreSQL に任せることができるため、Subscribe 側のみの実装を行うだけで良いのは楽であった。

Feedly Mini が動かなかった

Chrome

ページ右下にアイコンは出るものの無反応だった。

サードパーティクッキーが原因。
設定→プライバシー→コンテンツの設定→Cookie→例外の管理→[*.]feedly.com を許可
で解決。

PCとAVアンプをHDMIで接続するときの問題点を解決しよう

現在PCとAVアンプを接続する方法として一般的な方法はHDMIです。
最近のグラフィックカードはサウンドデバイスとしても働くので、AVアンプのHDMI INにPCを接続するだけでHDMI OUTに接続したモニタ(やテレビ)から映像が出て、音声もAVアンプから出力されます。

ただし、以下の問題があります。

  • サウンドのみの出力ができない (映像出力が必ずセットになる)
  • AVアンプの電源を切ると映像出力が切れ、更にサウンドデバイスを見失う
  • 機種(AVアンプ/グラフィックカード)にもよるが、AVアンプの電源を切るとディスプレイ自体も見失う (デュアルモニタにしていたらシングルモニタ環境になる)

1番目はどうしようもないです。
クローンモードにするなり、2枚目として使うなりしましょう。ただしクローンモードにするとHDCPが使えなくなります。

2番目以降は結構切実です。
DVIで接続しているモニタは電源を切っても認識されっぱなしですが、HDMIだとそうもいきません(たぶん)。

ディスプレイ自体を見失うのでDirectXなゲームをしていたりすると、だいたい落ちます。
また、サウンドデバイスがどこかにいくので、音も行方不明になります。もう一度AVアンプの電源を入れてもだいたい戻ってきません。

更に悪いことに、これはAVアンプのメーカーにも寄りますが、出力先のディスプレイの電源のON/OFFだけでも上記現象が起ることがあります(SONY製は少なくともそうなってます)。
これはAVアンプのEDID情報(使用できる解像度やサウンドのコーデック、周波数 etc. な情報)が出力先のディスプレイに連動しているためで、たとえば私が使用している SONY STR-DN2030 では、

  • ディスプレイをON → サウンドデバイス名はモニタの名前
  • ディスプレイをOFF → サウンドデバイス名は SONY AVSYSTEM

と変化し、ディスプレイの解像度情報としても、ONのときはディスプレイが使用できる解像度まで、OFFのときはAVアンプがサポートしている解像度(STR-DN2030では4K解像度まで選べました)が現れます。

要は「AVアンプのON/OFFでHDMI接続を見失うのが悪い」ということになります。
言い換えれば「常に一定のEDID情報を出力してくれていればいい」ということです。

長々と書いてきましたが、世の中には「EDIDエミュレータ(信号保持器)」という、これまたそのまんまな機械が存在し、これを使えば一気に解決します。

こんなふうに接続します:
PC --[HDMI]-- EDIDエミュレータ --[HDMI]-- AVアンプ --[HDMI]-- ディスプレイ

EDIDエミュレータには「クローンモード」というモードが存在し、ディスプレイのEDID情報を学習し、それを常にシステム側(ここではPC)に送出してくれます。
一度AVアンプから送られるEDID情報を学習させることで、AVアンプの電源がどうなっていようが、出力先ディスプレイがどうなっていようが、PCからは「つながっている」と見なされるので、ディスプレイを見失ったり、サウンドデバイスを見失うことは無くなります。

AVアンプの電源が切れていると映像出力も途切れるのは解決できませんが、AVアンプのパススルー機能を使用することで一応解決できます。デメリットとしてAVアンプの電源が切れていても電力を消費してしまいますが。
ちなみに、パススルーを設定していても、EDIDエミュレータを使用しない場合、電源のON/OFFで一度ディスプレイを見失います。

最後にうちの環境です:

Picasa の Instant Upload アルバムから他のアルバムに「移動」できなくなっていたのでなんとかした

Picasa JavaScript Greasemonkey

昔はできていたのにね。
コピー→削除はだるいし、Picasa WebAlbum 上で複数の写真を削除するとやけに時間かかるしでやってられません。

なのでグリモン書きました。

Gauche から wkb (well-known binary) を読み込めるライブラリを書きました

Scheme Gauche

wkb とは

GIS 分野において、点や線分、多角形を扱うフォーマットです。
binary の名の示すとおり、バイナリフォーマットです。

wkb と wkbhex

wkbhex は wkb を hex 表記したもので、0x00 0x80 0xff 0x00 というバイト列ならば、"0080FF00" といった文字列表記になります。

みどころ

本題です。
wkb パーサは実のどころどうでもよくて、wkbhex をバイナリポートに変換するルーチンです。

(define (read-hex-string :optional (iport (current-input-port)))
  (glet* ([higher (read-char iport)]
          [lower (read-char iport)])
    (string->number (string higher lower) 16)))

(define (open-input-hex-string str)
  (make <virtual-input-port> :getb (cute read-hex-string (open-input-string str))))

read-hex-string はポートを引数に取ります。
glet* は and-let* のジェネレータ版です (要 gauche.generator)。
and-let* は #f が評価された時点で停止し、#f が返りますが、glet* は (eof-object) が評価された時点で停止し、(eof-object) が返ります。
read-hex-string は2バイト(正確には2文字)をポートから読み込み、読み込まれた文字列を数値に変換して返します。
つまり、ポートに "FFFE" という文字列が残っている場合、255, 254 という数値を順番に返します。
ポートの終端までたどり着いた場合は glet* により (eof-object) が返ります。
つまり、read-hex-string はジェネレータです。

open-input-hex-string は、文字列を受け取り、バイナリとして読み込めるポートを返します。
wkbhex to wkbトランスレータです。
read-hex-string は 0-255 の数値、もしくは (eof-object) を返すので、virtual-input-port の :getb に突っ込むだけでバイナリデータを読み込むことができるポートを作成できます。

ちなみにエラーチェックがないのでつけるべきですね ;-)

そのうち

Gauche のジェネレータは面白いのでなんか書きたい

課題

  • 2次元座標にしか対応していないので、多次元座標に対応させたい
  • wkb/wkbhex の出力もしたい

Galaxy Nexus で apt-X を使えるようにしたい (手詰まり中)

Android Galaxy Nexus

apt-X 対応の Bluetooth ヘッドフォンを購入したものの、スマホ側 (Galaxy Nexus) が対応していないので SBC にフォールバックされて 64kbps MP3 を聞いているような感覚に陥りながら、外で聞く用だからそんな音質こだわらねーしwwwと粋がりながらも、ふぇえやっぱり音質しょっぱすぎるよう;;; となっているアカウントがこちらになります。

なんか最近のシャープのナントカってやつとか、HTC J とかは apt-X に対応してるっていうじゃん?Galaxy S3 も対応してるっていうじゃん?じゃあ移植してみればいいじゃん?
ということで、HTC One S の ROM から物故抜いて移植を試みてみました。

  • /system/lib/libbt-aptx-4.0.3.so
  • /system/lib/bluez-plugin/audio.so
  • /system/lib/hw/audio.a2dp.default.so
  • /system/etc/bluetooth/audio.conf (APTXSources=1 を記述)

が関係ありそげ。こいつら全部 Galaxy Nexus に放り込んでみたら、見事にブートアニメーションから先に進みませんでした。残念無念。
AOKP のソースを眺めてみたところ、やはりというか、hw/audio.a2dp.default.so はハード依存っぽく、そら単純に移植しても動かんわなー。
apt-X 自体はソフトウェアレイヤなはずなので、どうにかすればいけそうな気はします。
が、ライセンスなどの関係でこいつらのソースコードが出てくることはなさそうなので、手詰まり。