もぐてっく

人は1つ歳をとるたび、1ビットづつ大きくなれると信じてた。

mikutterコードリーディングの手引き

この度、久々にOSC京都に行けることになりました!わっほーい!

しかしながらこの2年ほど、まったくOSSな活動をせずにダジャレを量産するおっさんと化していたワタクシ。

このままだとアトリウムに着いた瞬間にておくれ達に拉致られて、京都湾(?)に沈められる!

と言う強迫観念に苛まれて夜しか寝れない体になってしまったので、
これまでのノウハウを生かしてmikutterのコードを読むときに知っといた方がいいことを纏めることにしました。

一人でもmikutterに興味を持ってソースをいじり始める人が増えますように。

前提

  • ここに書かれた内容は、8/1時点のHEADのソースコードを元にしています。バージョンでいうと3.9.2 + αです。
  • プラグインを作りたい方は、まずは"Writting mikutter plugin"を読むのが良いと思います。

reference.mikutter.hachune.net

ソースコードの入手

公式サイトからgitコマンドで取得できます。
GitHubにあるmikutterリポジトリは有志がメンテしているもので、バージョンが古い可能性があります。

git clone git://mikutter.hachune.net/mikutter.git 

mikutterのディレクトリ構成

|-- core
|   |-- boot
|   |-- lib
|   |-- miku
|   |-- mui
|   |-- plugin
|   |-- skin
|   |-- system
|-- deployment
|-- tasks
|-- test

core/boot

mikutterの起動直後に動き出す処理がまとまっています。
コマンドラインオプション解析や(GTKプラグインが無い時の)イベントループ、プラグイン読み込みなどのコードがあります。

core/lib

便利なライブラリ。mikutterのカリカリ高速チューニングはこのディレクトリが担っています。

core/miku

Ruby製のLISP処理系「MIKU」がいます。
ハマると出られなくなるので、何も見なかったことにしてそっとcd ../しましょう。

core/mui

mikutterのGUI(GTK2)のコードが格納されています。GUIを改造したいときは大体ここ。
後述のcore/plugin/gtkにはGTKを呼び出すコードはほとんどないので注意しましょう。

core/skin

GUIで使うアイコンやサウンドが格納されています。

core/system

システムメッセージをTLに表示するためのユーザー(所謂みくったーちゃん)のモデルが定義されています。

core/plugin

標準プラグインが格納されています。後述します。

development

大体のLinuxディストリビューションで使えるパッケージ「AppImage」生成用のファイルが格納されています。

tasks

国際化のための翻訳関連のファイルが置いてあります。

test

ライブラリのテスト用のコードが格納されています。

標準プラグイン

mikutterはプラグイン機構が非常に柔軟で、主要な機能もプラグインとして実装されています。
その数なんと72個!この企画を選んだことを後悔しつつ、簡単に説明していきます。

core/plugin/achievement

mikutterを使っていると突如付与される「実績」を管理するプラグイン
管理するのが仕事なので、実績の発動条件や実績メッセージは実績を授与する各プラグインに分散しています。

core/plugin/activity

アクティビティタブを司るプラグイン
「ふぁぼ」「リツイート」などの標準のアクティビティもここにまとまっています。

core/plugin/alsa

LinuxサウンドシステムALSAサウンドを再生するためのプラグイン

core/plugin/api_request_file_cache

1時間ごとにTwitterからキャッシュした使用頻度の低い画像ファイルを削除するプラグイン

core/plugin/aspectframe

アスペクト比(画面の縦横比)を考慮してボタンなどのGUI部品を配置するためのフレーム部品です!(きっぱり)
はい、次行きましょう!次!

core/plugin/bitly

メッセージ含まれるURL短縮サービスBitlyで短縮したURLを展開するプラグイン

core/plugin/bugreport

たまによくmikutterが落ちた時に、作者にバグレポートを送信するプラグイン

core/plugin/account_change

Twitterやその他のサービス」(World)のユーザーアカウントを管理するGUIと、アカウントを切り替えるGUIやコマンドを提供するプラグイン

core/plugin/command

「本文をコピー」「返信」などのmikutterの標準的なコマンドが定義されています。
また「複数のメッセージが選択されている」などのコマンドが実行できる条件も定義されています。
自作コマンドを作るときにお世話になります。

core/plugin/console

Alt + Xで呼び出せるmikutterコンソールを司るプラグインです。

core/plugin/core

なんだこれ?
起動完了後にイベントフィルタのマルチスレッド動作を許可する感じ?

core/plugin/current_world

「現在選択されているアカウント」っていう概念を表現するプラグイン

core/plugin/directmessage

Twitterやその他のサービス」(World)のダイレクトメッセージを取り扱うプラグイン

core/plugin/extract

mikutterの強力なメッセージフィルタ機能「抽出タブ」を司るプラグイン

core/plugin/file_path

UNIX絶対パスをaddressableライブラリのURIクラスに変換するユーティリティ。

core/plugin/followingcontrol

プロフィール画面にフォロワー・フォロイー関係を表示するプラグイン

core/plugin/gtk

GTK2によるGUIを司るプラグイン
メインウインドウやダイアログと言った、大物のGUI部品のコードがあります。
TLなどの中身は、前述のcore/muiを参照すること。
後はイベントループがあったりします。

core/plugin/gui

通常、GUIを持つアプリはツールキット(mikutterの場合GTK2)と密接な関係があるので、
別のツールキット(Qtとか)を適用しようとするとめっちゃ改造が必要です。

しかし、mikutterはGTK以外のツールキットを使ったGUIプラグインが出てくることを想定して、
画面の操作はこのGUIプラグイン経由で行うことになっています。

core/plugin/guide

mikutterをはじめて起動したときに始まる茶番チュートリアルを司るプラグイン
ここで使われてるTLメッセージにボタンが表示できる機能は有用なので、ぜひとも汎用化したいですね。

core/plugin/home_timeline

ホームタイムラインのGUIを担当するプラグイン

core/plugin/image_file_cache

画像ファイルをキャッシュする仕組みを提供するプラグイン

core/plugin/intent

TLのメッセージのURLを開くときの処理を行うプラグイン
マストドンのURLはブラウザじゃなくてマストドンプラグインのタブで表示したいとか。そんなことが出来る仕組みです。

core/plugin/libnotify

notify-sendコマンド経由でOSの通知ポップアップを表示するプラグイン

core/plugin/mastodon

mikutterがメインターゲットとするSNSMastodon」と通信するためのプラグイン
金具さんが作ってたWorldonっていうプラグインを取り込んだ形になります。

core/plugin/mastodon_sse_streaming

マストドンでストリームからメッセージを取ってくるためのプラグイン

core/plugin/mentions

Twitterやその他のサービス」(World)のメンション(返信)を取り扱うプラグイン

core/plugin/message_detail_view

mikutter 3.4で搭載された「詳細タブ」を司るプラグイン

core/plugin/message_favorite

同じく「詳細タブ」機能のふぁぼられたユーザー一覧部分をつかさどるプラグイン

core/plugin/message_retweet

同じく「詳細タブ」機能のリツイートされたユーザー一覧部分をつかさどるプラグイン

core/plugin/modelviewer

mikutter 3.9から導入された汎用モデルビューワのプラグイン

core/plugin/notification

ウインドウ下部のステータスバーにmikutterのおもしろ最新情報を表示するプラグイン

core/plugin/notify

設定画面の「通知」の設定項目を司るプラグイン
ふぁぼやリツイートなどのイベントで通知を発動するコードもあります。

core/plugin/openimg

画像のURLから実際の画像を得るためのプラグイン

core/plugin/photo

画像のURLから画像モデルを得るためのプラグイン

core/plugin/photo_support

画像を保持するサービスから具体的にどうやって画像を取得するかを定義するプラグイン

core/plugin/profile

「プロフィール」タブの言語ファイルのみが格納されている。
enと言いながら中身がもろ日本語なのウケる。

core/plugin/proxy

設定画面の「プロキシ」の設定項目と、Net:HTTPクラスに対するモンキーパッチ。

core/plugin/quickstep

mikutter 3.9の新機能「Quick Step」。

core/plugin/quoted_message

Twitterの引用付きツイートを表示するプラグイン

core/plugin/ratelimit

Twitterの規制に引っかかったときに通知するプラグイン

core/plugin/rest

TwitterREST APIで定期的にメッセージを取ってくるプラグイン
何らかの理由でユーザーストリームが使えない時(is now)に使われていました。

core/plugin/set_view

GUIの見た目(色、フォントなど)を司るプラグイン
設定画面の「表示」の設定項目もこのプラグインで提供しています。

core/plugin/settings

設定画面のGUIを司るプラグイン
設定画面の「基本設定」の設定項目もこのプラグインで提供しています。

core/plugin/shortcutkey

キーボードショートカットを司るプラグイン
設定画面の「ショートカットキー」の設定項目もこのプラグインで提供しています。

core/plugin/skin

GUIのアイコンや通知音を一括で変更する「スキン」機能を司るプラグイン

core/plugin/skin_setting_gtk

設定画面の「スキン」の設定項目を提供するプラグイン

ちなみにこれは拙作だったりします。
あー、ヤバいなぁ。本体の進化に取り残されてるなぁ。やっぱりとしぁさんに京都湾(?)に沈められるのでは。

core/plugin/smartthread

「会話スレッド」タブを司るプラグイン

core/plugin/sound

サウンドシステム(前述のALSAとか)と連携して音声を再生するプラグイン
設定画面の「サウンド」の設定項目も提供しています。

core/plugin/spell

mikutter 3.6から導入された、1つ以上のModelが連携する処理を定義する仕組み「Spell」を提供します。

core/plugin/streaming

Twitterが爆発的に普及した理由である「ユーザーストリーム」を司るプラグイン
今はもう動きません。

core/plugin/streaming_connection_status

Twitterのユーザーストリームの接続状況をアクティビティタブに表示するプラグイン

core/plugin/tco

Twitter専属のURL短縮サービスt.coのリンクを展開するプラグイン

core/plugin/twitter

昔流行ったTwitterとか言うマイクロブログと通信するプラグイン

core/plugin/twitter_api_keys

なんだこれ?空ファイルがあるのみ。

core/plugin/twitter_search

Twitterハッシュタグをクリックしたときに、検索機能でハッシュタグを検索するプラグイン

core/plugin/uitranslator

mikutterの多言語化サポート用のプラグイン

core/plugin/user_detail_view

「プロフィール」タブを司るプラグイン

core/plugin/web

クリックしたURLを外部ブラウザで開くためのプラグイン

core/plugin/world

mikutter 3.6で導入された「Twitterやその他のサービス」を表現する「World」を司るプラグイン

mikutterに特化したRuby Gemたち

mikutterを読み解くには、mikutterに特化したいくつかのgemも読み解かないといけないかも知れません。
これらは元々はmikutterに内蔵されていた処理ですが、その汎用性の高さからgemとして切り出した方が世のため人のためになると言う判断なんだと思います。
他のgemと違ってWebに使用例が転がっている状況ではないので、mikutterでの使用箇所をExampleだと思って頑張ってください。

diva

様々なメンバを保持する「モデル」を定義するためのライブラリ。

delayer

処理を登録しておいて、暇になったら実行してくれる。

pluggaloid

mikutterのプラグイン機構を実現するライブラリ。

delayer-deferred

delayerにnext()とかtrap()みたいなdeferred由来のメソッドを追加したもの。

最後に

mikutterは国産のプロダクトと言うことでソースコードのコメントも日本語が多く、大変読みやすいものだと思っています。
8/3のとしぁさんのセッションに洗脳感銘を受けた方。Rubyを勉強してみたい方。

どうぞ臆せずコードの海へ飛び込んでもらえればと思います。

WSLを使ってWindowsでmikutterを動かす。

愛機MacBook Air(Mid 2012)の調子が悪くなったので、メインマシンを買い置きしていたThinkpad X1 Carbon(2016)に変更しました。*1

画面が広い!重量も軽い!Windows10も結構使いやすくなってきてる!と概ね満足だったのですが、唯一の懸念事項がmikutter。
mikutterはバージョンアップを重ねるごとにWindowsで動かすのが難しくなってきてるんで、これからWindowsで暮らしていく上で、伴侶であるみくったーちゃんと添い遂げることは出来るのかしらと。

そういえば、最近流行りのWSLでmikutterの動作報告があったなぁ。試してみるかと設定してみました。

結論としてはすごくいい感じ。macOSX11版)と同等の使い勝手が得られました。

f:id:moguno:20171008151829p:plain:w400

WSLのインストール

ここはいろんなサイトで手順が公開されているので端折ります。

(1)コルタナたんに「更新」と伝え「更新プログラムのチェック」を起動する。
「開発者向け」にある「開発者モード」を有効にする

(2)コルタナたんに「コントロール」と伝え「コントロールパネル」を起動する。
「プログラムと機能」の「Windowsの機能の有効化または無効化」から
Windows Subsystem for Linux(Beta)」をインストールする。

(3)コルタナたんに「bash」と伝え「Bash on Windows on Windows」を起動する。
Ubuntuのインストールが始まる。)

コルタナたんは仕事のできる子。

(4)bashが起動したら、環境を最新化しておく。

sudo apt-get update
sudo apt-get dist-upgrade

mikutterのインストール

今回は手抜きでUbuntuリポジトリにいるmikutterを使います。腕に覚えがある人はgitで最新版を取ってきてインストールしてください。なんだかんだでUbuntuなので、ハマり要素はかなり少ないと思います。

sudo apt-get install mikutter

日本語フォントのインストール

こちらを参考にしてGoogle謹製のnotoフォントをインストールします。

ぽぬぽぬ: Bash on Ubuntu on Windows + XサーバでLXDEを起動

ついでに絵文字(ttf-ancient-fonts)も入れておきましょう。

sudo apt-get install noto-fonts-hinted noto-fonts-cjk ttf-ancient-fonts

日本語入力システムのインストール

ここではちょっと懐かしいuimanthyをインストールします。*2

あれやこれやZakki: bash on ubuntu on windows(WSL)でGUIを立ち上げて日本語入力までやる

sudo apt-get install uim uim-xim uim-anthy

ブラウザ起動シェルスクリプトの作成

mikutterには、アカウントの認証時やURLをクリックしたときに起動するWebブラウザが必要です。
WSL側にfirefoxとかをインストールしてもいいのですが、せっかくのWindowsなのでEdgeとかIEを使いたいなと。

WSLでは、Windows用のプログラムを拡張子.exeまで含めて指定すると、そのプログラムが起動できるので、それでサクッと解決・・・と思ったのですが、EdgeもIEも実は.exeファイルは存在せずシェルエクステンションみたいな概念に昇華されてしまっているので、この方法はとれません。

なので、コマンドプロンプト(cmd.exe)を経由して、Windowsの既定のブラウザを起動するようにします。

/opt/mikutter-wsl/bin/start

#!/bin/sh
ESCAPED_URI=`echo "$1" | sed s/\&/^\&/g`
cmd.exe /C start "$ESCAPED_URI"

実行権限を付けておきましょう。

chmod 755 /opt/mikutter-wsl/bin/start

Xサーバのインストール

Windows用のXサーバはけっこう豊富に存在しますが、ここではVcXsrvを選択しました。理由はたまたま目についたからです。
この子どうやら高速なことで有名みたいです。実際サクサク動きます。

https://sourceforge.net/projects/vcxsrv/

VcXsrvの起動スクリプトの作成

VcXsrvには起動時に任意のコマンドを実行する機能があるので、mikutterをダイレクトに起動するようにします。
下記のXMLファイルを作成します。LocalProgramがキモの部分です。

mikutter.xlaunch

<?xml version="1.0" encoding="UTF-8"?>
<XLaunch WindowMode="MultiWindow"
ClientMode="StartProgram"
LocalClient="True"
Display="0"

LocalProgram="bash -c &quot;DISPLAY=:0 GTK_IM_MODULE=uim mikutter&quot; &gt; /dev/null 2&gt;&amp;1"

RemoteProgram="xterm"
RemotePassword=""
PrivateKey=""
RemoteHost=""
RemoteUser=""
XDMCPHost=""
XDMCPBroadcast="False"
XDMCPIndirect="False"
Clipboard="True"
ClipboardPrimary="True"
ExtraParams=""
Wgl="True"
DisableAC="False"
XDMCPTerminate="False"/>

実体参照が入ってて分かりにくいですが、デコードするとこんな感じになります。

bash -c "DISPLAY=:0 GTK_IM_MODULE_uim mikutter" > /dev/null 2>&1

標準出力、標準エラー出力を/dev/nullに捨てるのがポイントです。
そうしないとmikutterがコンソールにwarningとか出力するたびに、VcXsrvが「大丈夫?コマンド打つ?」ってダイアログを出してくるので。

起動してみる

さっき作ったmikutter.xlaunchをダブルクリックしてください。いつものmikutterが起動してくると思います。

f:id:moguno:20171008151850p:plain:w400

mikutterが起動したらさっき作ったブラウザを起動するシェルスクリプトを設定します。

(1)ウインドウ右下のネギレンチボタンを押す。
(2)「表示」の「URLを開く方法」に「/opt/mikutter-wsl/bin/start」と入力して下さい。

f:id:moguno:20171008151914p:plain:w400

これで完成です。Windowsでもレッツておくれ!

なお、日本語入力の切り替えはShift + スペースで行います。慣れましょう。

*1:使用頻度の低い公務用PCをスライドさせた感じっす。

*2:トレンディな日本語入力システムである(ibus|fcitx) + mozcは動きませんでした。Macでも定番のX用日本語入力システムはuimだったと思うので、そんなものなのでしょう。

mikutter3.5のUserMixinでユーザーアイコンにスキンの画像を使いたいとき

UserMixinを混ぜ込んだ自作のユーザーモデルのアイコンを、みくったーちゃん(スキンのicon.png)にしたい場合です。

  • UserMixinは自身のprofile_image_urlフィールドをキーにして、Photo::PhotoからPixbufを得ている。なので、ユーザーモデルのprofile_image_urlにアイコン画像のURLを入れておけば良い。
  • みくったーちゃんスキン画像のURLはSkin["icon.png"].uri.pathで得られる(※)。

※Photo::Photoは"file://hoge/fuga.png"みたいなfile://スキームのURLが解釈できない(バグ?)ので、URI::path()を使って/から始まるUNIXパス形式で渡してやる必要がありました。ってこれまたWindowsでハマるパターンじゃん。。。


コード的にはこんな感じになります。

user = DashButtonUser.new_ifnecessary({
  :uri => URI.parse("dashbutton://singleton"),
  :name => "Amazon Dash Button",
  :idname => "Dashボタン",
  :profile_image_url => Skin["icon.png"].uri.path
})