Saving an image via Akahukuplus

虹裏にはたまにいわゆる虹裏ブラウザを話題とするスレが立つのだが、赤福プラスが俎上に載ることはめったにない。そしてまれに載った際はたいてい、使いにくくて機能の少ないクソと烙印を押されるのがオチなのであった。不満の一つに画像の保存をローカルに対して行えないという点があるようなので、それに対応してみよう。

もちろん Chrome のエクステンションからローカルのファイルシステムを直接は操作できないのだが、拙作のLFO – ローカルファイル操作ライブラリを通すと不思議なことにファイルの読み書きは自由にできちゃうので、これを使う。すでに wasavi と kokoni が LFO を使用している。これに赤福プラスも加わることになる。

LFO 側で適宜設定を行ったあと、赤福プラスの設定でファイル保存名テンプレートを[cci]ピクチャ/ふたば/$SERVER/$BOARD/$YEAR-$MONTH/$DAY-$SERIAL.$EXT[/cci]などと設定し、使用するストレージを[cci]local[/cci]にする。で、保存リンクを押せばそのとおりの場所に保存される。これだけ。あとは画像を開いた際に自動的に保存、みたいなオプションもあればいいかな。

技術的には、任意のパスに保存するには画像本体の保存に先駆けていわゆる [cci]mkdir -p[/cci] 的な動作が必要になる。現バージョンの LFO はこの機能を持っていないのでまずそこから実装する必要があった。LFO というか、Chrome Apps の FileSystem API 自体にそんな素敵な機能はない。従って、パス中の個々のディレクトリごとに一つずつ掘っていくという繰り返し処理を書くことになる。これを FileSystem API を直接呼びながら実現するとコードがものすごく汚くなりそうだったので、Promise を使うようにした。ついでなので、他の LFO のコマンドもすべて Promise ベースで動作するように書き直した。

ちなみにこの機能は Chrome 専用だ(Chrome Apps が Firefox や Opera で動くならその限りではないが)。

Prevent from executing an inline script

以前にも書いた気がするが、赤福プラスはふたばが返す html をまるごと変換し、上書きする。従って、元の html に記述されている画像や、スクリプトや、インラインフレームの読み込みはまったく不要で、ブロックする必要がある。ブロックした上で、変換後の html から改めて読み込まなければならない。

そんなわけで、Chrome では WebRequest API を用いてそれを実現していたのだが、ただ一つ html に直接記述された script 要素、つまりインラインスクリプトの実行は見逃していた。まあこれを見逃しても src 属性付きのスクリプトの読み込みはブロックしているので、大抵のインラインスクリプトはなんちゃらが定義されていませんエラーになって実害はないのだが、気にはなる。

しかし、Chrome のエクステンションの API を眺めてみてもインラインスクリプトの実行をブロックする機能は見つからない。いやあることはある。例えば WebRequest でレスポンスヘッダに CSP を忍ばせて、インラインスクリプトを除外するとか、あるいは ContentSettings でふたば上のみの javascript の実行を禁止するとか。が、リファレンスを読んでみるといずれも html を読み込んでから DOMContentLoaded までの短い期間だけスクリプトの実行を抑制するという要件にはちょっと合ってない。

いろいろ調べてみたところ、content script で MutationObserver を用いて script 要素が DOM ツリーに追加された瞬間をキャッチし、type を text/javascript とか application/javascript から、スクリプトとして実行されないものに書き換えるとそういう動作を実現できるようだ。で、用が済んだら、DOMContentLoaded ハンドラで disconnect() すればいい。完璧だ。

ただし Firefox ではこの type 書き換え法が効かないので、その代わり script 要素の beforescriptexecute イベントをリスンして適宜 preventDefault() する。つまり実行タイミングを start_at にした content script から

var observer = new MutationObserver(ms => {
. function handleBeforeScriptExecute (e) {
. . e.target.removeEventListener(
. . . 'beforescriptexecute', handleBeforeScriptExecute, false);
. . e.preventDefault();
. };
. ms.forEach(m => {
. . m.addedNodes.forEach(node => {
. . . if (node.nodeType != 1 || node.nodeName != 'SCRIPT') return;
. . . node.type = 'text/plain';
. . . node.addEventListener(
. . . . 'beforescriptexecute', handleBeforeScriptExecute, false);
. . });
. });
});
observer.observe(document.documentElement, {
. childList: true,
. subtree: true
});

とこんな感じのコードを走らせる。

ちなみに Presto Opera だとこんな長いコードを書かなくても、

window.opera.addEventListener('BeforeScript', function(e){e.source=''}, false);

だけで実現できる。オーパーツすぎる…。

そういえば忘れていたけど、次のリリースから赤福プラスは Presto Opera をもうサポートしない。

Ubuntu broke

Ubuntu Server を入れている機械も 18.04.1 にあげようと do-release-upgrade を走らせたところ、見事に失敗して何やら dpkg の依存関係がめちゃくちゃになってしまった。あばばばば。

せっかくなので、新規インストールした。もともと 32bit 版だったのでいつかは入れ直さないといけなかった。従ってちょうどいい機会と言えばそうだ。

ネットワーク周りが netplan というもので管理するようになってたり、[cci]/etc/apt/sources.list[/cci] に universe が入ってなかったりが若干のつまづきポイントだったが大体滞りなく移行。

From crying 16yo to grieving 18yo

Xubuntu を 16.04 から 18.04.1 に上げたので諸々をメモ。

  • ターミナルで bdf/pcf フォントを使えなくなった。これはアップグレードの際はいつものことで、[cci]/etc/fonts/conf.d/[/cci] から no-bitmap.conf 的なものを削除し、yes-bitmap.conf だけを残す(なければ [cci]../conf.avail/[/cci] からシンボリックリンクを貼る)
  • ログインできない。正確にはパスワードをキーボードから打ち込んでも正しいパスワードと認識されない。スクリーンキーボードを使用すると通る。まだ原因は調べてない
  • ログイン直後、マウスの右ボタンを連続して押している状態になっているらしく、右クリックからのメニューがまともに使用できない。数分で勝手に治る。まだ原因は調べてない
  • xfce のパネルを縦置きしている。従来はテキストが縦方向に描写されていたのが横方向になってしまう。これは単に見た目の問題なので気にしないことにする。多分きっとおそらくそのうち修正される
  • xkb の設定がリセットされた。うちの機械では過去の記事の通りに [cci]/usr/share/X11/xkb/[/cci] 以下のファイルへ直接必要な設定を埋め込んでいるという若干行儀の悪いことをしているので当然だ。ホームディレクトリに設定を置くようにしてもいいのだが、そうするとたまにやらかしてリカバリーモードで起動せざるを得なかったときにぎょえーとなる。そういうわけで再び [cci]/usr/share/X11/xkb/[/cci] 以下のファイルを弄る
  • cifs のマウントの際 smb のプロトコル ver3 がデフォルトになったそうなのだが、そうすると Windows10 機との接続に失敗するようなので fstab で vers=1 を追記。対 Win10 なのになんで ver1 じゃないとだめなの? 暇があったらあとで調べる

それから、これはもしかしたら 16.04 の頃からだったのかもしれないのだが、Thinkpad USB Keyboard with trackpoint …のトラックポイントの加速度の設定が、どうも標準の設定画面からはできてない気がする。この辺を参考に設定。


$ xinput list
⎡ Virtual core pointer id=2 [master pointer (3)]
⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
⎜ ↳ Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint id=10 [slave pointer (2)]
⎣ Virtual core keyboard id=3 [master keyboard (2)]
↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)]
↳ Power Button id=6 [slave keyboard (3)]
↳ Power Button id=7 [slave keyboard (3)]
↳ Integrated Camera: Integrated C id=8 [slave keyboard (3)]
↳ Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint id=9 [slave keyboard (3)]
↳ C-Media Electronics Inc. USB PnP Sound Device id=11 [slave keyboard (3)]
↳ Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint id=12 [slave keyboard (3)]

製品名は “Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint” とのことだ。

$ xinput list-props "pointer:Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint"
Device 'Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint':
Device Enabled (140): 1
Coordinate Transformation Matrix (142): 1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000
libinput Natural Scrolling Enabled (276): 0
libinput Natural Scrolling Enabled Default (277): 0
libinput Scroll Methods Available (278): 0, 0, 1
libinput Scroll Method Enabled (279): 0, 0, 1
libinput Scroll Method Enabled Default (280): 0, 0, 1
libinput Button Scrolling Button (281): 2
libinput Button Scrolling Button Default (282): 2
libinput Middle Emulation Enabled (283): 0
libinput Middle Emulation Enabled Default (284): 0
libinput Accel Speed (285): 0.500000
libinput Accel Speed Default (286): 0.000000
libinput Accel Profiles Available (287): 1, 1
libinput Accel Profile Enabled (288): 1, 0
libinput Accel Profile Enabled Default (289): 1, 0
libinput Left Handed Enabled (290): 0
libinput Left Handed Enabled Default (291): 0
libinput Send Events Modes Available (261): 1, 0
libinput Send Events Mode Enabled (262): 0, 0
libinput Send Events Mode Enabled Default (263): 0, 0
Device Node (264): "/dev/input/event3"
Device Product ID (265): 6127, 24585
libinput Drag Lock Buttons (292):
libinput Horizontal Scroll Enabled (293): 1

このデバイスに設定できるプロパティ群。個別の詳細は [cci]man xinput[/cci] で知るべし。で、[cci]/usr/share/X11/xorg.conf.d/[/cci] あたりに適当にファイルを作って中身を

Section "InputClass"
Identifier "Trackpoint tweaks"
MatchProduct "Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint"
MatchDevicePath "/dev/input/event*"

# The default driver now, so optional to put in this line.
# But does not hurt mentioning where the options will go.
Driver "libinput"

Option "Accel Speed" "0.5"
EndSection

こんな感じにする。

Installing Digital TV into Linux

ふだん家では Linux マシンがメインで、サブの Windows マシンはたまにテレビを見るときにつけるという感じだったのだが。ここに来てそれに接続していた15年選手のディスプレイがついに壊れてしまった。なんということでしょう。ブラタモリと鉄腕 DASH が見れなくなってしまうじゃないか。

Windows マシンへは Chrome Remote Desktop を通してリモートで操作する選択肢もあり、10fps くらいになるが一応メインマシンからテレビを見ることはできる…のだが、これが恐ろしく負荷が高い。大体30分もすると熱のために勝手に電源が落とされてしまう。

さてどうするか。ディスプレイが壊れたのだからディスプレイを新調すればいいのだが、実際のところ Windows マシンはほとんど使っていないのであった。ほとんど使ってないマシンに素敵なナウいディスプレイくっつけて何が楽しいのか。何も楽しくない。

それよりも、メインマシンで直接テレビが見られれば遥かに楽なのだが。でも Linux で地デジなんてできるの…? と思いつつググってみたら、なんと今では録画サーバーなるものを Linux マシンで組むことはごくごくあったりまえの行為らしい。そ、そうなんだ…。すごい。

ということでこのあたりを参考にチューナーとカードリーダーを入手してみたところ、実に簡単に VLC でテレビを視聴するところまでいけた。すごい。

このままでも視聴自体はできるのだが、チャンネルを変えたりするのが若干面倒なのでここから Chinachu の諸々を入れるといいようだ。これはまあ、そのうちにやりたい(やらないフラグ)。

CPU 負荷はだいたい10%くらい。すごい。ところでサブチャンネルを選択したい場合とかはどうするのかな。

Pikoyan

再放送中のけものフレンズには字幕が付いているのだが:

  • サーバルがジャパリバスの運転席を持ってジャンプする最後のシーン、「ぴこやん!」としか聞こえない。たぶん「とりゃー!」と言ってるのだと思うのだがぴこやんとしか聞こえない。自信がないので書き起こしでは地の文でごまかしてある。どうしよう
  • タイトルで○○ちほーとよくあるが、字幕ではカタカナのチホーである。これはアプリ版でもそうなので、そうなのかなと迷ったのだが、とりあえず書き起こしではひらがなで統一してある。しかしカタカナが正しいとなると、じゃあ「へいげんちほー」はセリフとしては(ライオンが 1 度セリフとして発音している)へいげんチホー? 平原チホー? ヘイゲンチホー? 判断する材料がない。どうしよう
  • 再放送のソースは最初に放送されたそれに比べて若干の修正が施してある。これは intro や outro のテロップも含まれる。甚だしいところでは、声優名が全然違うものになってたりする。これはどれを底とすべきだろうか
  • 再放送とはあまり関係ないが、サウンドトラックによって各 BGM の題名が明らかになったので、書き起こしにおいて BGM が奏でられるタイミングでその題名を追加したい。が、困ったことにサウンドトラックに収録されているのはサウンドトラック用に編集されたものであって、放送されたものに含まれるものとは若干違うのであった。また、常に BGM の最初から奏でられるわけではなく特徴的な最後の締めが多用されたりするパターンもある。どうしよう

Integration #2

そういうわけで、設定のうち例外リストとなっていたものを site overrides とした。こんな感じで記述する:

http://example.org/* * block
http://example.net/*.html * writeas=p

ディレクティブとして有効な行は、3つの要素からなる。左から順に URL パターン、CSS セレクタ、アクション。まず URL パターンと CSS セレクタで要素を特定する。例外リストの段階では CSS セレクタは省略可能で、省略した場合ユニバーサルセレクタを指定したことになっていたが、省略不可能になった。サイトすべての編集可能要素を対象にしたい場合はユニバーサルセレクタを明示的に記述する必要がある。

最後の要素がアクションで、これが [cci]block[/cci] であった場合は、そのパターンに合致する要素上では wasavi の起動が抑制される。それ以外であった場合、set コマンドの引数として wasavi 起動時に評価される。つまり、exrc の最後に付加される。

 * * *

これとは直接は関係ないのだけど、バックグラウンド側での設定の持ち方を変えた。従来は各ブラウザの差異を吸収するような抽象的なクラスを経由していたが、単に chrome の API を直接呼ぶようにした。

Integration

wasavi のオプションに、writeas というものがあり、こういう仕様になっている。つまり、contentEditable な要素に対して wasavi のバッファを書き戻す際、どのような DOM の構造にするかを指定する。たとえば [cci]p[/cci] なら、各行を [cci]p[/cci] 要素として書き戻す。

しかし、当然ながら contentEditable な要素の内部構造をどのように保持しているかはサイトごとにまちまちであり、ひとつに決められるものではない。そこで、writeas には 連想配列を表す json 文字列を代入することも許している。こんな感じで:

set writeas='{ \
"http://example.com/*": "div", \
"http://example.net/*": [ \
{ \
"selector": "#any-css-selector", \
"writeas": "textAndBreak" \
} \
] \
}'

連想配列のキーが URL パターンで、値が writeas という形式だ。または、さらに連想配列の配列を入れ子にして、CSS セレクタを指定することもできる。これにより、つまりサイトごとに writeas の定義を分けることができる。

ただ……見てのとおり exrc 中に改行のエスケープしまくりで書かないといけないので、とても面倒くさい。また [cci]set all[/cci] した時の見栄えもとてもよくない。もっと洗練された形式で保持する必要がある。

ところで、サイトごとに定義を振り分けているものは wasavi はもうひとつ持っている。サイト自身が提供するスクリプトとコンフリクトするか何かでうまく動かない場合に、wasavi の起動をしないという例外リストである。これは普通のプレインテキストで

http://example.com/
http://exmaple.org/ #some-id

のように行ごとに URL を書き連ねる。URL のあとに任意の個数の空白を挟んで CSS セレクタを記述することもできる。これと writeas の定義を統合できないだろうか。つまり例外リストではなく per-site overrides という扱いにしたい。

override := URL-pattern (CSS-selector)? action
action := allow-actions
"block"
allow-actions := allow-action ("," allow-action)*
allow-action := "writeas" "=" writeas-value
writeas-value: "div" | "p" | "textAndBreak" | "plaintext" | "html"

という感じだ。

Mapping more modes #2

そういうわけでいろいろ変更している。

  • 特定のキー入力を別の入力に置き換えるために、最新の wasavi は以下の 3 種のマップを保持している:
    1. normal マップ: normal モード時に参照される
    2. bound マップ: bound/bound_line モード時に参照される
    3. input マップ: insert/overwrite モード時に参照される

    これ以外のモードにおいてはリマップはできない。これは技術的にできないのではなく、単に現状ではそういうふうにしていないというだけだ。

  • マップに対するルールの操作は、ex コマンド [cci]map[/cci]、および [cci]map![/cci] で行う。既定では、前者は normal と bound マップの両方を、後者は input マップを対象にする。どのマップを対象にするかは、後述のアトリビュートで変更できる。
  • [cci]map[/cci] [cci]map![/cci] は、最小で 0、最大で 2 つの引数を取ることができる。さらに、引数の前にアトリビュートを置くことができる。
  • アトリビュートは、先頭が [cci][[/cci]、末尾が [cci]][/cci] であるカンマ区切りの文字列である。アトリビュートのコンポーネントとして有効なものは、上記のマップ名か、もしくは [cci]clear[/cci] [cci]final[/cci] [cci]noremap[/cci] のいずれかである。
  • アトリビュートにマップ名が含まれる場合、デフォルトマップの代わりにそのマップが選択される。
  • アトリビュートに [cci]clear[/cci] が含まれる場合、選択されたマップに定義されたルールをすべて削除する。引数は使用されない。
  • 引数が 0 個の場合、選択されたマップに定義されたルールをすべて表示する。
  • 引数が 1 個の場合、lhs に引数 1 が部分的にマッチするルールをすべて表示する。
  • 引数が 2 個の場合、引数 1 を lhs、引数 2 を rhs として、選択されたマップ全てに対してルールを登録する。この際、アトリビュートに
    [cci]final[/cci] または [cci]noremap[/cci] が含まれる場合は、再帰展開しないマップとして登録される。

 * * *

ところでここまで修正したのを Selenium でテストしようと思ったら、何やらおかしい。wasavi を起動させたあと、キー入力が行われない。つまり textarea に対する sendKeys() は動くのだが、wasavi 本体の iframe への sendKeys() が動作しないようだ。

以下の chromium の issue が関係しているかもしれない:
https://bugs.chromium.org/p/chromedriver/issues/detail?id=1777
https://bugs.chromium.org/p/chromedriver/issues/detail?id=1819

Throw a query to twitter

Twitter で検索する際、いろいろ特殊な文法が使えるらしい。例えば url: を前置して URL を書くと、それにリンクしているツイートを検索できるそうなので喜び勇んで appsweets.net とか github.com/akahuku とかやってみたのだが。それによってわかったことは、日本人はけものフレンズのセリフ起こしとかグラディウスとかには食いついてくるのだが、wasavi にはほとんど興味を示していないということだった。なるほど。