某botのこれまでとこれから
はじめに
これはあくあたん工房Advent Calendar 2018 の18日目の記事です. 僕はこれまでこのAdvent Calendarではずっとあくあたん工房の紹介をしてきました.少しでも興味があれば,他の記事も見て頂ければと思います.
さて,この記事は,
- 某botの開発の歴史: どういう経緯で作って,どんな技術を使ってきたのか
- 何がつらいのか: 開発面・運用面のつらい話
- これからどうしていくのか: 卒業までにやっておきたいと思っていること
の3本立てでお送りします.この記事を読んでいる弊学の人は一発でわかるであろう,某便利bot*1の作者は僕なのですが,色々悲惨でつらい感じなのでここで精算してスッキリしようという感じです.
某botの開発の歴史
そもそもbotを作ったきっかけ
- 大学の公式サービスが使いづらくて腹が立った
よくある話ですが,大抵大学のサービスっていうのはレガシーで,見づらくて,把握しづらくて,カオスです.
とにかくこういうのは待ってたって良くならないので,自分でなんとかするしか無いのです.というわけで作りました.
第一世代
Pythonを使用して作りました.当初の構成は簡単で,
- cronを利用して定期的にWebページをスクレイピング
- データベースで情報を管理
- 更新や新規情報の追加があればツイート
たったこれだけでした.この頃は確かまだPython 2.7を使ってRaspberry Piの上で動かしていて,ちょっと尖ったことと言えばSQLAlchemy*2を使ってSQLを書かずにやっていたことくらいでした.
やりたかったこととしては,
- 頻繁に追加される情報の取得
- 既存の情報の更新があった場合その差分の取得
- それらをツイートする
の3つなのですが,結局差分をとるところを実装しないまま終わってしまっています.
第二世代
まぁ人間しばらくすると満足いかなくなるもので,第一世代を運用していたときの僕は
など,たくさん不満を抱えていました.なのでしばらく後に現在稼働している第二世代を書きました.
特徴としては
- 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 |
このハッシュ値を使用して以下の様な方法でデータベースの情報と照合し,更新をツイートすることにします.
更新検知の実際の流れ
さて,ハッシュを用いて簡単に判別することができることを示しましたが,これで本当にうまくいくのでしょうか?
実際には『同一の初回掲載日と概要を持つ同一科目の連絡が複数存在する場合』という悪夢のような状況が存在します(何を考えてるんだほんとに…).
この場合,同一の識別ハッシュを持つデータが複数存在することになり,どれを更新すべきなのかを判定できない状態になります.
当時のプログラム的には先に見つけた方を更新していたため,同一のデータが複数回更新され,毎回ツイートされるというバグが起きました.
これに対する良い解決手法を,僕は未だに思いついていません.そこでとんでもない雑手法によって現在は解決しています.
- 各データについて,最後にその情報を取得した日時を「データ取得日時」として記録する.
- データの更新が無かった場合にも,この「データ取得日時」だけは常に更新する
- ある識別ハッシュと一致するハッシュを持つデータの一覧を取得し以下のループを実行する
- 更新ハッシュを比較して同じ値なら「データ取得日時」を更新して終了
- そのデータの「データ取得日時」と現在時刻の差が5秒以内であれば何もせず次のループへ
- 1にも2にも該当しなければデータを更新しツイート
- 全データのうち「データ取得日時」が更新処理開始よりも前であるものは削除
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の利用を検討しています.
が,研究と就活の狭間で身動きが取れない日々を送っているので後回しです…
一緒に開発やりたいっていう人を大募集中です.僕は一人ではもうモチベが持続しそうにありません.
卒業したら…
アカウントごと消すつもりをしています.不便になってもそれは僕のせいじゃなくて学務課のせいなので,「その公式っぽいTwitterアカウントは学祭の実況しかできないのか?」と偉い人のところへ乗り込んで説教してください.
まとめ
- クソサービスは待ってても良くならない.自分で改善していけ.
- クソサービスはデータ形式もクソ.学生に向かってなんだその半角カタカナは.
- Pythonを書く時はtype hintsを付けないと後々の自分が苦しむぞ
- Goはいいぞ
さて,あくあたん工房はこんな苦悩とは無縁の活動をしているので大変のびのびやっています.うちに入ったからこれを保守しないといけないなんていうことは全くないので,Golangをやりたい/やってる人,是非ご参加ください.Slackの #gophers
チャンネルがいつでもあなたをお待ちしています.
それでは.
*1:クソリプを飛ばしたりD進を煽ったりラーメンの判定をしない方のbotです
*3:毎回sshしてgit pullしてDBマイグレーションは自分でSQLを叩いていた.環境はpyenvで切っていたが,まだ使い方がよくわかっておらず何度もシステムのPythonから叩いて怒られた.
*4:なおこれはTwitterのUserStream廃止に伴って完全に死にました
*5:最近だとUEFI更新中に電源を落としてしまって起動すらできなくなったりしました
*6:全面的に僕が悪いので反省しています
*7:大学の就活情報をあてにしていないので…
*8:無料ではレコード数制限がありますが,削除されたものはDBから消していくようにすると1000レコードくらいで収まるのではないかと思います.
自作キーボードに手を出した
沼だってわかってはいたんだ……
↑最高に可愛い
きっかけ
Twitterを眺めてたらMint60っていうキーボードの予約が始まったっていうツイートが流れてきたのでちょっとググってみた.
なにこれ可愛い…インターン終わりの夜で疲れ果てていたので記憶が曖昧なのだけれど,気が付くとご予約完了のメールが届いていたのだ…
到着〜組み立て
参考までに今回の発注は下記の様な内容でした.
- スターターセット
- アクリルカバー:マットクリア
- 軸:Gateron Silent Red
- キーキャップ:ABS Cubic
公式のブログが非常に丁寧なこと,他の方のブログでも紹介されていることからここは割愛します*1
以下自分のツイートから抜粋.
#Mint60 きた!!!! pic.twitter.com/2KTaNsz5WE
— 院生 (@pudding_info) 2018年10月6日
後はキーをはめます #Mint60 pic.twitter.com/ZMLZ4t3WBL
— 院生 (@pudding_info) 2018年10月6日
めちゃくちゃかわいくないですか… #Mint60 pic.twitter.com/RVKBpvBj4m
— 院生 (@pudding_info) 2018年10月6日
はいかわいい〜〜優勝〜〜〜 #Mint60 pic.twitter.com/AINU3qV2g8
— 院生 (@pudding_info) 2018年10月6日
個人的注意点
ほとんどの部分ではつまずくことは無かったし,半田付けを日常的にやっているわけではない身でもそれほど難易度が高いとは感じませんでした.ゆかり屋さんに感謝.
テープLED
Mint60では裏面にテープLEDがついており,いろいろな色やパターンで光らせることが出来ます.大変かわいらしい.
ただこれを貼る列(左シフト〜Bのある列とN〜Enter下の初期ではFnになっているキーの列)の半田付け後の突き出たピンと干渉することがあるようです.これは公式にも書いてあり,この部分の突き出たピンをしっかり切るよう注意書きがされています.実際に手元でその列だけ死ぬという事案があり,確かめてみたところテープLEDを離すと正常に動作しました.
最初はテープLEDの裏紙を剥がして普通にくっつけていたのですが,どうも不安定になってしまうため,裏紙を貼り直しました.アクリルボードと基板の間で挟まれているため張り付いている必要は無いと判断してこうしたのですが,もう少し安全にしたいなら絶縁テープ等を咬ますべきでしょう.
また,そもそも最初からここはピンを切り詰める等しないとテープLED挟んだときに厚みが出過ぎてアクリルボードが少し反るようです*2.
スタビライザー
公式に組み立て方が載っています.「金属のパーツを固定する爪を持っている樹脂パーツ」と,「そうではない小さい方のパーツ」を組み合わせるときは底面に切り欠きがあるためそれが合うようにしないといけません.写真が無くて申し訳ないのですが一回失敗してしまい折れそうになりながら外しました.
また,右側キーボードの最下段の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年前の記事であることから結局公式ドキュメントなどを参考にすることも多かったです.
基本的にはデフォルトのキーを入れ替える程度かとは思いますが, LT()
やALT_T()
,CTL_T()
などのような単押しと長押しで異なるキーを使用する設定を使う場合,僕はデフォルトの設定ではかなりタップの時間が厳しく,思っていたような動作が出来ませんでしたので対抗策を紹介します.
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コンテナが出たのでそれに合わせて更新しました.
できるだけ環境依存を減らしたいので,Dockerコンテナでビルドします.
上記公式ドキュメントにありますが,やり方は以下の通りです.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での書き込みが失敗する件についてはこちらに記述しました.
ビルドした.hexファイルを書き込みます.上記ブログを参考に,Dockerコンテナではできないそうなので*3avrdude
をインストールする
$ brew install avrdude
後は
- キーボードをUSBでPCと接続
- 裏面のリセットボタンを押す
- 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用のパームレストが良い感じの長さなのでこれをぶった切れば…とか考えています.自作界隈の方々はパームレストどうしてるのでしょう.
xmodmapとxcapeで消耗するのはもうやめよう
きっかけ
ひさしぶりにデスクトップでデュアルブートしていたUbuntu 16.04を開こうとしたらくたばっていて、仕方なく18.04を新規インストールしてセットアップしているとかつてキーマップをどういじっていたのか思っていた操作ができない…
xmodmapとxcapeでなにやらいろいろやっていた覚えがあったのでやってみるも、fcitxにぶっ壊されてしまうし、キーマップがうまく動作していないように感じ、セットアップに無限に時間が吸われていってしまう…
調べてみるとxkeysnailというツールがたいへんよさそう。
というわけで導入、設定してみました。
環境
$ 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とは
LinuxでうごくPython3で書かれたキーマッパです。Linux版Keyhacというところ。
xmodmapやxcapeがsudoなしに実行できるのに対し、xkeysnailはより低レイヤー(evdev
およびuinput
)で動作するためにsudoを必要とします。
そのため、使用に際し、権限を持たないユーザは使用できません。
また、X windowに依存するため、自動起動させる際にも注意が必要でしょう。今回はGnomeのautostart機能を利用しています。
- メリット
- fcitxと競合して設定が消し飛んだりしない
- アプリケーションごとにキーマップを設定可能
- multi strokeをサポート(
C-x
C-c
をC-q
にしたりできる) - キーリマップするだけでなく、Pythonの関数などを割り当てたりもできる
- いわゆるoneshot modifierが簡単にできる
- 低レベルレイヤーで動作するため、ほとんどの場合においてキーマップが動作する
- デメリット
- 情報が少ない
- sudoが必要
導入
xkeysnailのインストール
pipでインストールできます。
$ sudo apt install python3-pip $ sudo pip3 install xkeysnail $ which xkeysnail /usr/local/bin/xkeysnail $ xkeysnail -h
こうなります。
設定
基本的にここの2つの内容をそのまま踏襲させていただきました。
自動起動
自動起動させる方法も上記サイトに載ってはいますが、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/
以下に配置すれば自動起動の一覧に表示されます。
キーリマップ
注意
僕は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], })
Mac風IME切り替え
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の設定からそれぞれ入力メソッドのオン/オフに指定します。
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で検証しています。
やっていることはセットアップの内容をそのまま記述しているだけです。
- 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
を書いたりするよりいいと思うのでおすすめです。