ぽよメモ

レガシーシステム考古学専攻

某botのこれまでとこれから

はじめに

これはあくあたん工房Advent Calendar 2018 の18日目の記事です. 僕はこれまでこのAdvent Calendarではずっとあくあたん工房の紹介をしてきました.少しでも興味があれば,他の記事も見て頂ければと思います.

esa-pages.io

esa-pages.io

さて,この記事は,

  • botの開発の歴史: どういう経緯で作って,どんな技術を使ってきたのか
  • 何がつらいのか: 開発面・運用面のつらい話
  • これからどうしていくのか: 卒業までにやっておきたいと思っていること

の3本立てでお送りします.この記事を読んでいる弊学の人は一発でわかるであろう,某便利bot*1の作者は僕なのですが,色々悲惨でつらい感じなのでここで精算してスッキリしようという感じです.

botの開発の歴史

そもそもbotを作ったきっかけ

  • 大学の公式サービスが使いづらくて腹が立った

よくある話ですが,大抵大学のサービスっていうのはレガシーで,見づらくて把握しづらくてカオスです.
とにかくこういうのは待ってたって良くならないので,自分でなんとかするしか無いのです.というわけで作りました.

第一世代

Pythonを使用して作りました.当初の構成は簡単で,

  1. cronを利用して定期的にWebページをスクレイピング
  2. データベースで情報を管理
  3. 更新や新規情報の追加があればツイート

たったこれだけでした.この頃は確かまだPython 2.7を使ってRaspberry Piの上で動かしていて,ちょっと尖ったことと言えばSQLAlchemy*2を使ってSQLを書かずにやっていたことくらいでした.

やりたかったこととしては,

  • 頻繁に追加される情報の取得
  • 既存の情報の更新があった場合その差分の取得
  • それらをツイートする

の3つなのですが,結局差分をとるところを実装しないまま終わってしまっています.

第二世代

まぁ人間しばらくすると満足いかなくなるもので,第一世代を運用していたときの僕は

  • RasPiサーバにログインしないと見れないログ
  • 死ぬほど面倒くさいデプロイ*3
  • Twitterの文字制限で続きが見れなくてDBをのぞき見る馬鹿らしさ

など,たくさん不満を抱えていました.なのでしばらく後に現在稼働している第二世代を書きました.

f:id:pudding_info:20181213001938p:plain
新バージョンのアーキテクチャ

特徴としては

  • Python3に書き換えた
  • 対象サイトへのログイン回りを自前で書き直した
    • mechanizeのPython3対応が見込めなかったため
  • 全てDockerのエコシステムに載せた
    • GitHubへpushしたらDocker HubでAutomated Build
    • Dockerコンテナでデプロイ
  • Fluentdにログを集約しSlackへ流すようにした
  • UserStreamを監視してリアルタイムな返信機能を付加した*4

くらいで,コア部分はほぼ変更していません.エラーが出たときSlackからパッと見れるのは楽でした.ただ結局マイグレーションは手動という…
当然機能的にも大きく変わっておらず,ここでも更新分の差分をとっていません.なぜでしょうね.

さらに,まだまだ何も分かっていない初心者だった当時の自分の設計がかなり雑でもはや実装を読み解くのは容易ではなくなってしまいました. なぜか非同期処理にハマっていた節があり,どう考えても不要なところを非同期にしようとしていたりします.

完全に負の遺産ですね.

何がつらいのか

開発面のつらい話

対象サイト上のデータ

ここでは適当なデータを使います.分かる人は脳内で良い感じに補完してください.
まず,スクレイピング対象のデータ形式を示します.

科目名 担当者 曜日 時限 概要 詳細 掲載日 更新日
講座A A太郎 1 連絡 定規を持ってきてください 2018/ 12/18 2018/ 12/18
講座B B子 3 教室変更 001から002へ変更です 2018/ 12/16 2018/ 12/18

細かい部分は省きましたが,概ねこんな感じのデータになっています.また,新しいデータの追加やデータの更新は以下の様な仕様で行われる事とします.

  • 新規データはこのテーブルの一番上に追加される.
    • このとき更新日と作成日は同じ値を取る
  • 更新時には詳細の内容と更新日を登録する.掲載日や概要,科目の情報は変化しない.
  • 同一科目の連絡が複数掲示されることもある

データの識別

データベースで管理するために.まずは各データを一意に識別する必要があります.つまり,どのデータがどのように更新されたかを識別するためには,更新の前と後でデータの対応付けができないといけないということです.

難しいのはこれらのデータそれぞれについて,実際の対象サイトが使用するデータベース上にあるはずの一意なidがどこにも示されていないため,データを識別することが容易でないということです.
そこで現在はこれらのデータを下記の様に2つのセクションに分けて考えています.

科目名 担当者 曜日 時限 概要 詳細 掲載日 更新日
講座A A太郎 1 連絡 定規を持ってきてください 2018/ 12/18 2018/ 12/18
講座B B子 3 教室変更 001から002へ変更です 2018/ 12/16 2018/ 12/18

赤がデータの識別用,青はデータが更新されたかどうかを判別する用のセクションになります.授業の情報や初回の掲載日は変化しないと考えられるため,それらを使用して識別することにします.

これらのデータについて,各々の持つ情報を一つ一つ照合しても良いですが,ここではより簡単のため,これらのセクションをそれぞれ全て1つの文字列にまとめてハッシュ化して使用します.

同じ文字列からは常に同じハッシュが得られ,文字列が変化するとハッシュも変化するため,これらの値を比べることでデータの比較が可能になります.
実際に上記の講座Aの情報をハッシュにするとこうなります.

ハッシュ種別ハッシュ値
一意識別ハッシュ6351264e3a66aed80ccf31b3056a5d187d58f481
更新判別ハッシュe36c0ec693d6d6034f3e4cf1adec8736f6bece27

このハッシュ値を使用して以下の様な方法でデータベースの情報と照合し,更新をツイートすることにします.

Webから情報を取得
Webから情報を取得
取得した連絡のリストが
空になるまでループする
取得した連絡のリストが<br>空になるまでループする<br>
ハッシュを作成
ハッシュを作成
識別ハッシュが
一致するデータは
あるか?
識別ハッシュが<br>一致するデータは<br>あるか?
更新ハッシュが
一致しているか?
更新ハッシュが<br>一致しているか?
Yes
Yes
No
No
DBへデータを挿入
DBへデータを挿入
ツイート
ツイート
ループ終了
ループ終了
No
No
Yes
Yes
DB上のデータと
比較する
DB上のデータと<br>比較する<br>

更新検知の実際の流れ

さて,ハッシュを用いて簡単に判別することができることを示しましたが,これで本当にうまくいくのでしょうか?
実際には『同一の初回掲載日と概要を持つ同一科目の連絡が複数存在する場合』という悪夢のような状況が存在します(何を考えてるんだほんとに…).

この場合,同一の識別ハッシュを持つデータが複数存在することになり,どれを更新すべきなのかを判定できない状態になります.
当時のプログラム的には先に見つけた方を更新していたため,同一のデータが複数回更新され,毎回ツイートされるというバグが起きました.

これに対する良い解決手法を,僕は未だに思いついていません.そこでとんでもない雑手法によって現在は解決しています.

  1. 各データについて,最後にその情報を取得した日時を「データ取得日時」として記録する.
    • データの更新が無かった場合にも,この「データ取得日時」だけは常に更新する
  2. ある識別ハッシュと一致するハッシュを持つデータの一覧を取得し以下のループを実行する
    1. 更新ハッシュを比較して同じ値なら「データ取得日時」を更新して終了
    2. そのデータの「データ取得日時」と現在時刻の差が5秒以内であれば何もせず次のループへ
    3. 1にも2にも該当しなければデータを更新しツイート
  3. 全データのうち「データ取得日時」が更新処理開始よりも前であるものは削除

5秒というのは何の根拠もなく適当に決めた値で,どう考えても実装が良くないなと思っています.
とはいえここしばらくはこれでうまくいっているようです.すごい.なんでや.

その他の考慮事項

他にもデータ処理の過程でいくつか対応を行っています.

  • なぜか半角空白や全角空白,タブ文字が濫用されるので前後空白の除去と2つ以上の空白を1つの空白に置換すること
  • なぜか半角カタカナや全角括弧が多用されるため,それらを正規化すること
  • 詳細の内容にリンクが含まれることもあるため,それを取得すること

これを他のニュースだったり,休講だったりの内容にも適用しています.

運用面のつらい話

責任とコスト

サーバの運用費は自腹です.元々他の用途もあってサーバを稼動させているのでいいと言えばいいのですが,自分しか使っていないサービスならともかく,フォロワー数が4桁いるbotを止めるのは少し躊躇することもあり,サーバを気軽に止められないのが少しつらいです.
また,例えうまくいくことがわかっていても別のトラブルシュート*5にかかりきりになって移行が遅れたり,掃除してたらコンセント引っこ抜いちゃってサーバがダウンしたりするので*6,他人にも提供しているサービスを自宅で運用するのはあんまり嬉しくないです.

そもそも自分のために作っただけなんだから気にしなくてもいい,といえばそれはそうなんですが,現状を鑑みるにそれはあまりにも身勝手でしょう.経緯はどうあれ,それで知名度を上げようと画策したりもしたので,ある程度責任は持つつもりをしています.

モチベーションの低下

僕は今M1です.想定どおりに単位がとれれば来年からは授業がない(はず)なので,気になる情報は奨学金関連だけです*7
正直言ってモチベーションは0です.お金にもならないですし,技術的に面白いことをちょっとやってみようかな,くらいの気持ちでどうにか前を向いています.

このbotのこれから

では最後に今後どうするかの話をして終わりにしたいと思います.

更新検知をやめる

開発のつらい話でも述べましたが,更新の検知をせず,全て新規情報が追加されたかどうかで判別すれば先に述べた問題は起きません.
2つに分けたりせず,全体でハッシュを取って識別すれば良いだけです.

内容の差分が見たかったため更新検知を目指していましたが,実際その機能がどれだけ必要かというと全くもって不要でしょう.
そもそもそこまでやっておいてdiff取る程度のことすらしていないことからも,いかに僕がその機能を必要としていないのかが窺い知れます.設計段階で気づいて欲しかった.

Pythonがつらいのでやめる

Pythonはすごく便利な言語ですが,素人のコードは絡まったミシン糸より厄介になりがちです.もう今となっては過去のコードを頑張って読み解く気力がありません.特に最近はDjangoばかり触っていたのでSQLAlchemyの使い方を忘れました.
また,Pythonは型の情報が得づらいことが読みづらいコードに拍車をかけており,最近ではtype hintsがちゃんと認知されてきましたが,当時の僕は中途半端にしか使えていません.

これらを踏まえ,Goで作り直そうと思っています.デプロイが容易であり,静的型と比較的厳格なformatterやlinterによって書くコードに一貫性を持たせることが出来ます.
結局頑張ってPythonでtype hintsを手動で付けるより,そもそも型のある言語でやったほうが良いのではないかというのが今のところの結論です.

オンプレがつらいのでやめる

Dockerコンテナ化して以降はそこまででもないですが,自宅サーバにデプロイするのはやはりあまり意味が無いなと考えていました.
最近サーバを入れ替えてGitlabのホストを辞めたこと,Jenkins等を建てたくないこと,sshのポートを開けるメリットが薄いので閉じていること,などの要因が重なりオンプレでは自動デプロイが面倒なので今後はクラウドでやりたいと思っています.

また,誰かがこれを必要としたときに簡単に使えるようになっていることが望ましいと思っているので,

  • 登録・利用が簡単
  • 料金体系が分かりやすい
  • CI/CDが可能
  • できるだけ無料でホストできる
  • DBも使える*8

という要件を満たすherokuを検討しています.Google App EngineでGoを使うことも考えましたが,2nd generationへの移行がごちゃついていること,ロックインされがちであることから避けました.

新しい技術への挑戦

Goで書くとかクラウドに載せるとかではもうあんまりわくわくしないので,単なるbotではなくAPI化して,JSONではなくProtocol Bufferを使いたいと思っています.
ただherokuはgRPCに対応していない*9こと,要件的にも十分足るだろうという判断でtwirpの利用を検討しています.

github.com

が,研究と就活の狭間で身動きが取れない日々を送っているので後回しです…
一緒に開発やりたいっていう人を大募集中です.僕は一人ではもうモチベが持続しそうにありません.

卒業したら…

アカウントごと消すつもりをしています.不便になってもそれは僕のせいじゃなくて学務課のせいなので,「その公式っぽいTwitterアカウントは学祭の実況しかできないのか?」と偉い人のところへ乗り込んで説教してください.

まとめ

  • クソサービスは待ってても良くならない.自分で改善していけ.
  • クソサービスはデータ形式もクソ.学生に向かってなんだその半角カタカナは.
  • Pythonを書く時はtype hintsを付けないと後々の自分が苦しむぞ
  • Goはいいぞ

さて,あくあたん工房はこんな苦悩とは無縁の活動をしているので大変のびのびやっています.うちに入ったからこれを保守しないといけないなんていうことは全くないので,Golangをやりたい/やってる人,是非ご参加ください.Slackの #gophers チャンネルがいつでもあなたをお待ちしています.

それでは.


*1:クソリプを飛ばしたりD進を煽ったりラーメンの判定をしない方のbotです

*2:Pythonのいわば定番的なORMです

*3:毎回sshしてgit pullしてDBマイグレーションは自分でSQLを叩いていた.環境はpyenvで切っていたが,まだ使い方がよくわかっておらず何度もシステムのPythonから叩いて怒られた.

*4:なおこれはTwitterのUserStream廃止に伴って完全に死にました

*5:最近だとUEFI更新中に電源を落としてしまって起動すらできなくなったりしました

*6:全面的に僕が悪いので反省しています

*7:大学の就活情報をあてにしていないので…

*8:無料ではレコード数制限がありますが,削除されたものはDBから消していくようにすると1000レコードくらいで収まるのではないかと思います.

*9:Does Heroku have plans to support HTTP/2? - Heroku Help

自作キーボードに手を出した

沼だってわかってはいたんだ……

f:id:pudding_info:20181008005134j:plain

最高に可愛い

きっかけ

Twitterを眺めてたらMint60っていうキーボードの予約が始まったっていうツイートが流れてきたのでちょっとググってみた.

eucalyn.hatenadiary.jp

なにこれ可愛いインターン終わりの夜で疲れ果てていたので記憶が曖昧なのだけれど,気が付くとご予約完了のメールが届いていたのだ…

到着〜組み立て

参考までに今回の発注は下記の様な内容でした.

  • スターターセット
    • アクリルカバー:マットクリア
    • 軸:Gateron Silent Red
    • キーキャップ:ABS Cubic

公式のブログが非常に丁寧なこと,他の方のブログでも紹介されていることからここは割愛します*1

eucalyn.hatenadiary.jp

syonx.hatenablog.com

以下自分のツイートから抜粋.

個人的注意点

ほとんどの部分ではつまずくことは無かったし,半田付けを日常的にやっているわけではない身でもそれほど難易度が高いとは感じませんでした.ゆかり屋さんに感謝.

テープLED

Mint60では裏面にテープLEDがついており,いろいろな色やパターンで光らせることが出来ます.大変かわいらしい

ただこれを貼る列(左シフト〜Bのある列とN〜Enter下の初期ではFnになっているキーの列)の半田付け後の突き出たピンと干渉することがあるようです.これは公式にも書いてあり,この部分の突き出たピンをしっかり切るよう注意書きがされています.実際に手元でその列だけ死ぬという事案があり,確かめてみたところテープLEDを離すと正常に動作しました.

最初はテープLEDの裏紙を剥がして普通にくっつけていたのですが,どうも不安定になってしまうため,裏紙を貼り直しました.アクリルボードと基板の間で挟まれているため張り付いている必要は無いと判断してこうしたのですが,もう少し安全にしたいなら絶縁テープ等を咬ますべきでしょう.

また,そもそも最初からここはピンを切り詰める等しないとテープLED挟んだときに厚みが出過ぎてアクリルボードが少し反るようです*2

スタビライザー

eucalyn.hatenadiary.jp

公式に組み立て方が載っています.「金属のパーツを固定する爪を持っている樹脂パーツ」と,「そうではない小さい方のパーツ」を組み合わせるときは底面に切り欠きがあるためそれが合うようにしないといけません.写真が無くて申し訳ないのですが一回失敗してしまい折れそうになりながら外しました.

また,右側キーボードの最下段のShiftキーの位置は3箇所から選べるのですが,このとき最も右側に寄せてスタビライザーを付けると,残り二つのキーはスイッチを通常と逆向きに付ける必要があります.これは基板を見ると分かるのですが,みんなSpaceキーを分割した形を好むのかあまり作例が無く戸惑ってしまいました.自分を信じて半田付け.

ファームウェアをカスタム

せっかくProMicroが載った自作キーボードを手に入れたのだから配列を自分好みにしないともったいない!というわけで自分でファームウェアをカスタムします.

環境は以下

  • MacBook Pro 2017 (Touch Bar有り)
  • Homebrew 1.7.6
  • Docker for Mac 18.06.1-ce, build e68fc7a
  • avrdude 6.3

カスタマイズ

Mint60で初めてキーカスタマイズに手を出すみたいな人が居るのかはわからないのですが下記によくまとまっていました.ただリポジトリが移転してたり2年前の記事であることから結局公式ドキュメントなどを参考にすることも多かったです.

キーコードまとめ :: ErgoDox | Refills

基本的にはデフォルトのキーを入れ替える程度かとは思いますが, LT()ALT_T()CTL_T()などのような単押しと長押しで異なるキーを使用する設定を使う場合,僕はデフォルトの設定ではかなりタップの時間が厳しく,思っていたような動作が出来ませんでしたので対抗策を紹介します.

qiita.com

keyboards/mint60/keymaps/YOUR_KEYMAP_NAME/config.hを編集します.

// 長押しと判定するまでの時間(ms).デフォルトは200.
#undef TAPPING_TERM
#define TAPPING_TERM 200

// 他のキーを押さなければ長押し判定になるタイミングでも単押しにする
#define RETRO_TAPPING

// 単押し判定のタイミングでも他のキーを一緒に押したら長押し判定にする
#define PERMISSIVE_HOLD

このあたりは打ってみて気に食わないとか誤動作する等あれば良い感じにすればいと思います.

ビルド




[追記 2019/11/4]
QMK公式のDockerコンテナが出たのでそれに合わせて更新しました.

poyo.hatenablog.jp



できるだけ環境依存を減らしたいので,Dockerコンテナでビルドします.

docs.qmk.fm

上記公式ドキュメントにありますが,やり方は以下の通りです.YOUR_KEYMAP_NAMEを自分のカスタムした名前に読み替えてください.

$ KEYMAP_NAME=YOUR_KEYMAP_NAME
$ git clone https://github.com/qmk/qmk_firmware
$ cd qmk_firmware
$ cp -r keyboards/mint60/keymaps/default keyboards/mint60/keymaps/$KEYMAP_NAME
# 編集する
$ vim keyboards/mint60/keymaps/$KEYMAP_NAME/keymap.c
$ docker run --rm -e keymap=$KEYMAP_NAME -e keyboard=mint60 -v $PWD:/qmk edasque/qmk_firmware

これで.build以下にmint60_YOUR_KEYMAP_NAME.hexというファイルが出来ます.

僕はqmk_firmwareをフォークしましたがこれは好みかと思います.またビルド時にそれなりに時間がかかるため,ちゃんと完了するまで待ちましょう

フラッシュ




[追記 2019/11/4]

macOS Catalina 10.15でavrdudeでの書き込みが失敗する件についてはこちらに記述しました.

poyo.hatenablog.jp



buty4649.hatenablog.com

ビルドした.hexファイルを書き込みます.上記ブログを参考に,Dockerコンテナではできないそうなので*3avrdudeをインストールする

$ brew install avrdude

後は

  1. キーボードをUSBでPCと接続
  2. 裏面のリセットボタンを押す
  3. 7秒以内に下記コマンドを実行する
$ sudo avrdude -p atmega32u4 -c avr109 -P /dev/tty.usbmodem* -U flash:w:.build/mint60_$KEYMAP_NAME.hex

左右どちらも書き込めば完了です.

個人的に微妙な点

不満が全くないわけではないのでいくつか書きます.

スタビライザー

せっかくGateron Silent Redで通常のキーは静かなのにスタビライザーを使用しているキーではかなりカチャカチャ言います. またこれは個体差なのでしょうがたまたまEnterに使っているスタビライザーがちょっと擦れるキーキー言う音が鳴ってしまいます.そのうち潤滑油を塗ろうかな…

自作キーボードには詳しくないのですが,静かなスタビライザーというものは金を積むだけで手に入る物なんでしょうか?買えるなら欲しい…

バックスペースの位置

矢印キーがあるのは望んでいる人も多いので構わないのですが,普段HHKBを使っていた身からするとバックスペースの位置が遠いです. 親指で押せる位置に単押しバックスペースを配置していますが,今度は左上がバッククォートなのが気になってきます,右上のキーを1u二つにしてバックスラッシュとバッククォートを割り当て,バックスペースを一つ下に持ってこれると個人的には完璧だったなぁと.HHKBに慣れすぎて指がバグっているだけという気がしています.

左サイドの最下段キーの配置

右側では長いキーの位置を選択できたのに対して左では選べません.ここも親指をもうちょっと多用できると良かったなぁと思います.ただの好みという気はしますし,あんまりやる人も居なさそうです.

まとめ

機能性だけでなく見た目に大きく惹かれたため,綺麗に出来上がって大変満足です.ゆかりさんに感謝…!

身の回りでもこれを期にやってみたいみたいな声はいくつか聞こえているので再販是非頑張って頂きたい… 個人的にも今回買ったのは研究室用だったので家用にもう一台欲しい気持ちはあります.よろしくおねがいします!!!

HHKB用のパームレストが良い感じの長さなのでこれをぶった切れば…とか考えています.自作界隈の方々はパームレストどうしてるのでしょう.

f:id:pudding_info:20181008005018j:plain

*1:気が付くと手元でほとんど組み上がっていたので写真がほとんど無いなんて言えない……

*2:これは僕の半田付けが悪い可能性が高い.

*3:ほんまか?特に何もしなくてもリセット押すと/dev/tty.usbmodemXXXXXは生えた気がするのでコンテナにavrdudeをインストールして権限付けて/dev以下をマウントするだけではダメなん?という気がしている.avrdudeをインストールした後にアンインストールして試しただけなので依存関係でなにかインストールされていないといけないといういオチかもしれない…気が向いたら試す.

xmodmapとxcapeで消耗するのはもうやめよう

きっかけ

ひさしぶりにデスクトップでデュアルブートしていたUbuntu 16.04を開こうとしたらくたばっていて、仕方なく18.04を新規インストールしてセットアップしているとかつてキーマップをどういじっていたのか思っていた操作ができない…

xmodmapとxcapeでなにやらいろいろやっていた覚えがあったのでやってみるも、fcitxにぶっ壊されてしまうし、キーマップがうまく動作していないように感じ、セットアップに無限に時間が吸われていってしまう…

調べてみるとxkeysnailというツールがたいへんよさそう。

github.com

というわけで導入、設定してみました。

環境

  • Ubuntu 18.04 LTS
  • fcitx version: 4.2.9.6
  • Mozc-2.23.2815.102+24.2.oss
  • xkeysnail v0.1.0
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

xkeysnailとは

qiita.com

LinuxでうごくPython3で書かれたキーマッパです。Linux版Keyhacというところ。

xmodmapやxcapeがsudoなしに実行できるのに対し、xkeysnailはより低レイヤー(evdevおよびuinput)で動作するためにsudoを必要とします。
そのため、使用に際し、権限を持たないユーザは使用できません

また、X windowに依存するため、自動起動させる際にも注意が必要でしょう。今回はGnomeのautostart機能を利用しています。

  • メリット
    • fcitxと競合して設定が消し飛んだりしない
    • アプリケーションごとにキーマップを設定可能
    • multi strokeをサポート(C-x C-cC-qにしたりできる)
    • キーリマップするだけでなく、Pythonの関数などを割り当てたりもできる
    • いわゆるoneshot modifierが簡単にできる
    • 低レベルレイヤーで動作するため、ほとんどの場合においてキーマップが動作する
  • デメリット
    • 情報が少ない
    • sudoが必要

導入

xkeysnailのインストール

pipでインストールできます。

$ sudo apt install python3-pip
$ sudo pip3 install xkeysnail
$ which xkeysnail
/usr/local/bin/xkeysnail
$ xkeysnail -h

こうなります。

f:id:pudding_info:20180622230540p:plain

設定

基本的にここの2つの内容をそのまま踏襲させていただきました。

qiita.com

tmtms.hatenablog.com

自動起動

自動起動させる方法も上記サイトに載ってはいますが、Gnomeでのベストプラクティスがよくわからなかったので、~/.config/autostart/以下に.desktopファイルを配置することで起動スクリプトをログイン時に自動実行させることにしました。

まずは起動スクリプトです。

#!/usr/bin/env bash
if [ -x /usr/local/bin/xkeysnail ]; then
    xhost +SI:localuser:xkeysnail
    sudo -u xkeysnail DISPLAY=:1 /usr/local/bin/xkeysnail /etc/opt/xkeysnail/config.py &
fi

xhostコマンドでxkeysnailユーザにアクセスを許可し、バックグラウンドでxkeysnailを起動します。これを/etc/opt/xkeysnailに配置しておきます。

[Desktop Entry]
Type=Application
Version=1.0
Name=xkeysnail
GenericName=Keymapper
Exec=/etc/opt/xkeysnail/start-xkeysnail.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true

あとは上記を適当にxkeysnail.desktopなどとして~/.config/autostart/以下に配置すれば自動起動の一覧に表示されます。

f:id:pudding_info:20180623011230p:plain

キーリマップ

注意
僕はHHKB ProfessionalのUS配列を使用しています。

config.py

configはPythonで記述します。つまり文法はPythonそのものになるので下手なものを書けば当然syntax errorで落ちます。詳細はgithubのREADMEやexampleを見ましょう。

雛形はこうです。

# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

# something remap

Ctrl(またはCaps)単押しでEsc

まずはVimを使っている人には必須であろう(?)configを書きます。

# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

define_multipurpose_modmap({
    Key.CAPSLOCK: [Key.ESC, Key.LEFT_CTRL],
    # CapsLockではなくCtrlの場合
    # Key.LEFT_CTRL: [Key.ESC, Key.LEFT_CTRL],
})

今までxmodmapとxcapeでごにょごにょやっていたのは何だったのか…

SandS

Space単押しでSpace、他のキーと組み合わせてShiftにします。

# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

define_multipurpose_modmap({
    # SandS
    Key.SPACE: [Key.SPACE, Key.LEFT_SHIFT],
})

MacIME切り替え

Ctrl + Space使いづらいので…
スペースの両側にあるAltをそれぞれ変換、無変換キーに割り当て、fcitxでそれぞれIMEのオン/オフに使用します。

# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

define_multipurpose_modmap({
    Key.LEFT_ALT: [Key.MUHENKAN, Key.LEFT_CTRL],
    Key.RIGHT_ALT: [Key.HENKAN, Key.RIGHT_CTRL]
})

fcitxの設定からそれぞれ入力メソッドのオン/オフに指定します。

f:id:pudding_info:20180623012955p:plain

Vim like cursor

Ctrl + hjklで矢印キーを入力します。

# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

define_keymap(None, {
    K('C-h'): Key.LEFT,
    K('C-j'): Key.DOWN,
    K('C-k'): Key.UP,
    K('C-l'): Key.RIGHT,
}, "Vim-like cursor")

ログを見るとactive keymaps = [Vim-like cursor]が出力されていると思います。このキーマップはすべてのアプリケーションで動作することを示しています。

Escしたときに自動でIMEオフ

Vimを使っているときに入力モードから抜ける際、日本語入力のままになっていると毎回IMEをオフにしないといけないのが面倒です。
Vimを使っているかどうかを検知することはできませんが、ターミナルを触っている際にEscを押したら自動で無変換を送信し、IMEをオフにしましょう。

# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

# 標準の端末ならGnome-terminal
define_keymap(re.compile('Terminator'), {
    K('esc'): [K('muhenkan'), K('esc')]
}, "Esc and IME off")

ターミナルにフォーカスがあたっているときだけログにactive keymaps = [Esc and IME off]が出力されることがわかるかと思います。

ansible-role-xkeysnail

勉強のためにansible-galaxyにroleを登録してみました。Ubuntu 18.04 LTSで検証しています。

galaxy.ansible.com

やっていることはセットアップの内容をそのまま記述しているだけです。

  • python3-pipのインストール
  • xkeysnailのインストール
  • xkeysnail実行用グループおよびユーザの設定
  • /etc/udev/rules.d/40-udev-xkeysnail.rulesの記述
  • /etc/modules-load.d/uinput.confの記述
  • /etc/sudoers.d/以下にxkeysnail用のNOPASSWD設定を記述
  • 起動スクリプトおよびconfig.pyの配置
  • ~/.config/autostart/以下に自動起動.desktopファイルの追記

まとめ

しばらく常用していますがめちゃくちゃ安定していて最高です。
頑張って~/.Xmodmapを書いたりするよりいいと思うのでおすすめです。