xkeysnail の環境を整える

Ubuntu18.04 に環境を移行したので、その際に行った xkeysnail の環境構築手順をまとめておきます。

xkeysnail とは

github.com

インストール

README の通り

xkeysnail ユーザの作成

qiita.com

こちらの記事を参考にさせていただきました。ほとんど記事通りです。

起動周りのセットアップ

上記の記事に加え、下記も参考にさせていただきました。

poyo.hatenablog.jp

xkeysnail に関するディレクトリ構造

$HOME/
  .xkeysnail/
    config.py (設定ファイル)
    start.sh (起動スクリプト)
    stop.sh (停止スクリプト)
    restart.sh (再起動スクリプト)
  .config/
    autostart/
      xkeysnail.desktop (自動起動設定)

参考にした記事やその他の情報と大差ないですが、 config.py を弄って反映、ということをカジュアルにやりたかったので、自動起動だけでなく、停止や再起動を行うためのスクリプトを用意したのが特徴でしょうか。同じ理由で、設定ファイルやスクリプトはホームディレクトリ配下の .xkeysnail にすべてまとめるようにしています。

まだ運用を始めて日が浅いので、問題があるかもしれませんが各スクリプトは以下のような内容です。

start.sh

#!/usr/bin/env bash

exec >> $HOME/.xkeysnail/stdout.log 2>&1

xhost +SI:localuser:xkeysnail
sudo -u xkeysnail DISPLAY=$DISPLAY /usr/local/bin/xkeysnail -q $HOME/.xkeysnail/config.py &

-q オプションが効いてない気がする

stop.sh

#!/usr/bin/env bash

PID=`ps --no-heading -C xkeysnail -o pid | tr -d ' '`

if [ -n "$PID" ]; then
  sudo -u xkeysnail kill $PID
  echo "Stopped xkeysnail to kill $PID"
fi

restart.sh

#!/usr/bin/env bash

cd `dirname $0`

./stop.sh
./start.sh

echo "Started xkeysnail"

最後に

xkeysnail 本当に素晴らしいです。感謝

日報を聴くアプリ Nippou Player v1.0 リリース

Nippou Player for esa.io とは

esa.io で管理された「日報」を再生するためのアプリです。2017年に作ったものの、使うことも更新することもなく、という状態でした。

hidakatsuya.hateblo.jp

github.com

v1.0 をリリース

ふとした衝動から 1.0 をリリースしました。その新機能・変更点を簡単に紹介します。

f:id:hidakatsuya:20191116024331p:plain
Nippou Player for esa.io v1.0

しゃべらせるセクションを指定できるように

日報には「今日やったこと」「明日やること」など、組織やチームによっていろいろな項目があると思いますが、そのうち、しゃべらせたい項目を絞り込むことができるようになりました。

f:id:hidakatsuya:20191116015811p:plain
読ませるセクションの指定方法

この設定では、「いまの気持ち」と「ポエム」の項目だけしゃべってくれます。

PWA化

よく考えたら、機能的に Electron である必要はなくて Electron のビルドも面倒だったので、 PWA (Progressive Web Apps) へ。これによって、PC だけでなくモバイルでも簡単にインストールできるようになりました。

例えば Chrome だと、 https://hidakatsuya.github.io/nippou-player/ を開いて、

f:id:hidakatsuya:20191116021026p:plain
Chromeでのインストール方法

とすればインストール完了。なお、インストール後はアプリのサイズをプレーヤーっぽく縦長にすることをお勧めします。

その他改善

  • スピーチを若干改善
  • UI を若干改善

依存ライブラリの更新など

  • コードの構造を刷新
  • PWA化に伴い Electron を削除
  • Jest へ移行
  • Vue.js 2.6 へ更新
  • その他、依存ライブラリを最新へ

おわりに

セクションの絞り込み機能によって、関心がある内容だけさっと聴くことができるようになったので、最近は毎日使ってます。作業をしつつ、聴き流せるのが良いなぁと改めて思う今日この頃です。

その反面、毎日使ってるといろいろと気に入らないところもあって、

  • IBM Watson Text to Speech API など、リモートAPI のサポート
    • もうちょっと自然にしゃべって欲しい
  • 再生速度の変更機能
    • もうちょっと素早く聴きたい
  • 指定した日報からの再生開始、又は次へ/前へ機能
    • 「この日報だけ、もう一度聴きたいな」ということがたまにある
  • Alexa スキル
    • アプリの起動が面倒だし、Alexa がデスクにあるので

というあたりを今後やりたいなと思ってます。

簡単に使えるので esa.io を使ってる方はぜひお試しあれ。

Google Home を買ったので子供から親への連絡ツールにした

Google Home Mini を買いました。これは楽しい。

こんなことをできるようにした

いずれも、平日両親が不在のときに子供が帰宅した時を想定しています。Google Home はリビングに設置。

Google Home に「ただいま!」と言うと LINE グループにメッセージが届いて、いつ帰宅したかわかる

流れとしてはこんな感じです。

  1. 子供: 帰宅する
  2. 子供: Google Home に「ねぇGoogle、ただいま!」と言う
  3. Google Home:「おかえり」と返答する
  4. 私と妻が参加している LINE グループに「👶帰ったよ!」と届く
  5. 私と妻: 子供が帰宅したことを知る

Google Home に「二人に xxx って伝えて」と言うと LINE グループに「xxx」が届いて、子供からメッセージを受け取れる

例えば、子供が牛乳がなくなっていることに気づいたとしましょう。

  1. 子供: Google Home に「ねぇGoogle、二人に "牛乳がないよ" って伝えて」と言う
  2. Google Home:「二人に "牛乳がないよ!" と伝えました」と返答する
  3. 私と妻が参加している LINE グループに「👶牛乳がないよ!」と届く
  4. 私と妻: 牛乳がないことを知る

LINE はこんな感じです。

f:id:hidakatsuya:20180212110727p:plain

なぜか

  1. 共働きなので、どうしても両親不在のときに子供が帰宅することがあり、きちんと帰宅したか心配
  2. 子供たちから親への連絡手段は電話しかなく、ライトな連絡手段がほしい

主には (1) です。これまでは、帰宅時間を見計らって自宅に電話してました。

なぜこの方法か

直感的な「操作性」と行動としての「自然さ」ですね。

操作は「ねぇ、Google」と話しかけるだけですし、帰宅→「ただいま」というのは行動としても自然で、子供にも受け入れられやすく、今回のケースでは最適な方法だと思いました。後述しますが、プログラミングなど特別な設定も不要で簡単だったのも良かった。

どうやったか

IFTTT で、

  • (トリガー)Googleアシスタントで「ただいま」と話しかけたら、
  • (アクション)指定した LINE グループに「帰ったよ!」とメッセージする

というアプリを作るだけです。こんな感じです。

f:id:hidakatsuya:20180211195324p:plain

左画面で Googleアシスタントのトリガーを設定しています。「ただいま」「帰ったよ」「帰った」と話しかけると、Google Home が「おかえり」と答えてくれます。

そして、右画面で、LINEグループ(Recipient)に「帰ったよ!」と送信する、というアクションを設定しています。LINEグループには LINE Notify を招待しておきましょう。

最後に

スマートリモコン sRemo-R を買ったので、次は「ねぇGoogle、電気消して」を実現していきます。

フロントエンド勉強会でHTML帳票について話した

2017年12月8日、 第3回フロントエンド勉強会 in 山陰 で HTML 帳票について話してきました。

その際の資料とサンプルコードは以下に置いてあります。

speakerdeck.com

github.com

cognitom/paper-css

サンプルコードは、Paged Media (CSS3) の仕様などを調べつつスクラッチしたけど、完成寸前でやりたいことを全て実現してくれる paper-css というライブラリを見つけて笑いました。使いやすそう。

github.com

最後に

今までも何度かこの辺りの状況を調べてきたけど、割と実用的なものに近づいて来てるなぁという感じで、とても勉強になりました。勉強会も楽しかったです。スタッフのみなさん、参加者のみなさんありがとうございました。

日報をしゃべらせて聴くアプリ「Nippou Player for esa.io」を作った

みなさん日報書いてますか。

私が所属する Misoca でも、全員が毎日日報を書いています。ただ、メンバーが増えてくると、全員の日報を読むことが地味に大変だったりします。

日報を「聴く」

しかし読みたい。

もし、音楽のように日報を聴くことができたら、聴きながら作業もできるし読む辛さもなくなるかも。というわけで、勉強がてらアプリを作ってみたので紹介します。

Nippou Player for esa.io

f:id:hidakatsuya:20170303004032p:plain:w350

特徴

GitHub - hidakatsuya/nippou-player: A client for playing NIPPOU in esa.io.

  • esa.io の日報を音声として再生することができるデスクトップアプリ
  • Mac, Windows, Linux をサポート(ただし、Mac 以外は動作確認してない)
  • Electron, Vue.js 2.x, Photon, Webpack, axios, karma, mocha などでできている
  • 音声の再生は Web Speech API を使っている

必要なもの

Nippou Player は esa.io 上の日報を再生するアプリなので esa.io のアカウントが必要です。

使い方

Releases at Github より、パッケージをダウンロードできます。起動すると、

f:id:hidakatsuya:20170303010337p:plain:w350

こんな感じの設定画面が表示されるので、それぞれ以下のように設定します。

Access token for esa API

Nippou Player は esa API を使って日報を取得するので、そのアクセストークンの設定が必要です。アクセストークンの発行方法は以下を参考にしてください。

https://docs.esa.io/posts/102#3-0-0

日報一覧のパス

esa 上の一日分の日報一覧のパスを設定します。

日報/2017/03/03/
  ├ hidakatsuya の日報
  ├ hoge の日報
       :

例えば、日報をこのようなカテゴリで管理している場合は esa検索クエリの書式 を使って以下のように設定します。

in:日報/YYYY/MM/DD

YYYYMM は日付書式キーワードで、日報一覧を取得する際に日付に展開されます。「2017/3/3」の日報を取得するときは、

in:日報/2017/03/03

となる具合です。

この展開は Moment.jsmoment#format() によって行なっています。使えるキーワードは以下を参考にしてください。

Moment.js | Docs

以上で設定は完了です。メイン画面に戻ると日報一覧が表示されるはずなので、再生ボタンを押せばしゃべり出すはずです。

バグを踏んだりわからないことがあったら

ソースコードGitHub に公開しています。Pull request お待ちしております。または @hidakatsuya に質問してもらっても構いません。

github.com

作ってみて

感想

  • 今回初めて Electron アプリを作ったけど、別の OSS (thinreports-editor) でも Chrome Apps から Electron へ移行しようとしているので良い練習になった
  • 日報の内容をほぼそのまま Web Speech API に食わせているので、スピーチがわりと微妙
  • Vue.js の Single File Component が非常にわかりやすくて良い。次は Vuex を使って書き換えていきたいと思ってる
  • vue-clielectron-vue というテンプレートを使うと、babel とか webpack 周りが設定済みで、すぐにアプリ本体の開発に集中できて良い。ちょっと設定いじろうとすると何やってるかわかんなくて辛い部分もあるけど
  • UI は Photon が便利だった。が、微妙に足りてないところがある。メンテされていないのも気になるけど簡単で素晴らしい

今後

  • スピーチが微妙。ちょっと調べた感じ、やりようはありそうなので気が向いたら改善したいと思っている
  • ユースケース的にモバイルアプリに適しているツールなので、次は Swift の勉強がてら iOS 版を作ってみるつもり
  • TravisCI による自動ビルドがうまくいってなくてなんとかしたい。もしくは WerckerCI を使ってみようかと

Thinreports for PHP 0.8.1 をリリースした

まずは、軽く Thinreports for PHP の説明を。

Thinreports for PHP とは

Thinreports は、もともと Ruby 向けの帳票ツールとして作られた OSS で、私自身その開発者の一人だったりします。GUI ツールの Thinreports Editor で帳票レイアウトを作成し、Ruby ライブラリ thinreports-generator を使ってレイアウトをロードして PDF を生成します。

オープンソース PDF 帳票ツール for Ruby, Rails | Thinreports

Thinreports for PHP は、その thinreports-generator の PHP 実装となります。alpha リリースを経て、2015年11月に最初の正式版 0.8.0 をリリースしました。

github.com

こちらもご覧ください。

hidakatsuya.hateblo.jp

開発体制とか

開発は私一人でやってます。なかなかモチベーションが上がらず、今回のリリースも気がつけば1年越し。開発コミュニティの場としては、 Gitter の Community があります。開発に参加したい方、Thinreports に興味がある方は Gitter を覗いてみてください。

0.8.1

Bugfix が中心のマイナーアップデートです。詳細はこちらをどうぞ。

Release 0.8.1 · thinreports-php/thinreports-php · GitHub

変更点

ざっとご紹介しておきます。

#18 - Fix strokes and fills are not drawn correctly

  • 四角形・楕円形の背景色が PDF に描画されない
  • 四角形・楕円形・線形の色を none に設定しても PDF に描画されてしまう
  • テキストの色を none に設定しても PDF に描画されてしまう

これらの問題点を修正しています。結構クリティカルなやつです。

#19 - 0.8.x never supports .tlf generated with Thinreports Editor 0.9+

Thinreports Editor 0.9 以降、レイアウトファイル .tlf の内部フォーマットが大幅に変更されました。

github.com

Thinreports for PHP 0.8.x ではこの新しいフォーマットに未対応で、0.8.0 で新しいフォーマットの .tlf を読み込もうとすると、普通にプログラムエラーでコケてしまいます。そのため、 0.8.1 で IncompatibleLayout 例外をスローして原因をわかりやすくしました。

Next Major Version 0.9.0

現在開発中の 0.9.0 について少しだけ。

0.9.0 では、Thinreports Editor 0.9+ の新しいレイアウトフォーマットのサポートが中心となります。もちろん、古いレイアウトフォーマットへの互換性も維持します。

Support Thinreports 0.9 · GitHub

年内にリリースできればいいなー、とは思ってます。はい。

最後に

各種ツッコミをいただけるだけでモチベーションも上がって開発スピードが(きっと)上がります。興味のある方はぜひお試しください。

Thinreports 0.9.0 で .tlf ファイルの差分が取りやすくなった

2016.6.1 Thinreports 0.9.0 をリリースしました。

Thinreports 0.9.0 is out! | Thinreports - オープンソース PDF 帳票ツール for Ruby, Rails

0.9.0 以降の Editor では、.tlf ファイルが新しい形式で保存されるようになります。これによって、いわゆる「Diff 辛い問題」が解消されます。どういうことか簡単に紹介します。

古い形式

例えば、 test.tlf を Editor で編集して、一つのテキストブロックの文字色を #000000 から #ff0000 へ変更し上書き保存したとします。 0.8.0 以前の古い保存形式の場合、普通に diff を取ると次のような結果となります。

--- a/test.tlf
+++ b/test.tlf
@@ -1 +1 @@
-{"version":"0.8.2","config":{"title":"Test","option":{},"page":{"paper-type":"A4","orientation":"portrait","margin-top":"20","margin-bottom":"20","margin-left":"20","margin-right":"20"}},"svg":"<svg width=\"595.2\" height=\"841.8\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" preserveAspectRatio=\"none\" viewBox=\"0 0 595.2 841.8\"><g class=\"canvas\"><g class=\"s-text\" stroke-width=\"0\" fill=\"#000000\" fill-opacity=\"1\" kerning=\"auto\" letter-spacing=\"normal\" x-display=\"true\" x-id=\"\" id=\"goog_45091349\" font-size=\"18\" font-family=\"IPAMincho\" font-weight=\"normal\" font-style=\"normal\" text-anchor=\"start\" text-decoration=\"none\" x-width=\"72\" x-height=\"20.5\" x-left=\"20\" x-top=\"20\"><rect class=\"s-text-box\" stroke=\"none\" fill=\"#000000\" fill-opacity=\"0.001\" width=\"72\" height=\"20.5\" x=\"20\" y=\"20\"/><text class=\"s-text-l0\" xml:space=\"preserve\" stroke=\"none\" fill=\"inherit\" fill-opacity=\"1\" text-decoration=\"none\" x=\"20\" y=\"36\">\u30bf\u30a4\u30c8\u30eb</text></g><!--SHAPE{\"type\":\"s-tblock\",\"id\":\"text\",\"display\":\"true\",\"desc\":null,\"multiple\":\"false\",\"valign\":\"\",\"line-height\":\"\",\"line-height-ratio\":\"\",\"box\":{\"x\":20,\"y\":56,\"width\":164.1,\"height\":20.5},\"format\":{\"base\":\"\",\"type\":\"\"},\"value\":\"\",\"ref-id\":\"\",\"overflow\":\"\",\"word-wrap\":\"break-word\",\"svg\":{\"tag\":\"text\",\"attrs\":{\"x\":20,\"y\":72,\"xml:space\":\"preserve\",\"kerning\":\"auto\",\"letter-spacing\":\"normal\",\"id\":\"goog_45091350\",\"fill\":\"#000000\",\"fill-opacity\":\"1\",\"font-size\":\"18\",\"font-family\":\"IPAMincho\",\"font-weight\":\"normal\",\"font-style\":\"normal\",\"text-anchor\":\"start\",\"text-decoration\":\"none\"}}}SHAPE--><!--LAYOUT<g xmlns=\"http://www.w3.org/2000/svg\" class=\"s-tblock\" x-format-type=\"\" x-value=\"\" x-format-base=\"\" x-ref-id=\"\" kerning=\"auto\" letter-spacing=\"normal\" x-display=\"true\" x-multiple=\"false\" id=\"goog_45091350\" x-id=\"text\" fill=\"#000000\" fill-opacity=\"1\" font-size=\"18\" font-family=\"IPAMincho\" font-weight=\"normal\" font-style=\"normal\" text-anchor=\"start\" text-decoration=\"none\" x-width=\"164.1\" x-height=\"20.5\" x-left=\"20\" x-top=\"56\"><rect class=\"s-tblock-box\" stroke=\"none\" fill=\"#0096fd\" fill-opacity=\"0.2\" width=\"164.1\" height=\"20.5\" x=\"20\" y=\"56\"/><text class=\"s-tblock-id\" font-size=\"10.5\" font-family=\"Helvetica\" font-weight=\"normal\" font-style=\"normal\" text-decoration=\"none\" text-anchor=\"start\" kerning=\"auto\" stroke=\"none\" fill=\"#0096fd\" fill-opacity=\"1\" x=\"24\" y=\"67\">text</text></g>LAYOUT--></g></svg>","state":{"layout-guide":[]}}
\ No newline at end of file
+{"version":"0.8.2","config":{"title":"Test","option":{},"page":{"paper-type":"A4","orientation":"portrait","margin-top":"20","margin-bottom":"20","margin-left":"20","margin-right":"20"}},"svg":"<svg width=\"595.2\" height=\"841.8\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" preserveAspectRatio=\"none\" viewBox=\"0 0 595.2 841.8\"><g class=\"canvas\"><g class=\"s-text\" stroke-width=\"0\" fill=\"#000000\" fill-opacity=\"1\" kerning=\"auto\" letter-spacing=\"normal\" x-display=\"true\" x-id=\"\" id=\"goog_45091349\" font-size=\"18\" font-family=\"IPAMincho\" font-weight=\"normal\" font-style=\"normal\" text-anchor=\"start\" text-decoration=\"none\" x-width=\"72\" x-height=\"20.5\" x-left=\"20\" x-top=\"20\"><rect class=\"s-text-box\" stroke=\"none\" fill=\"#000000\" fill-opacity=\"0.001\" width=\"72\" height=\"20.5\" x=\"20\" y=\"20\"/><text class=\"s-text-l0\" xml:space=\"preserve\" stroke=\"none\" fill=\"inherit\" fill-opacity=\"1\" text-decoration=\"none\" x=\"20\" y=\"36\">\u30bf\u30a4\u30c8\u30eb</text></g><!--SHAPE{\"type\":\"s-tblock\",\"id\":\"text\",\"display\":\"true\",\"desc\":null,\"multiple\":\"false\",\"valign\":\"\",\"line-height\":\"\",\"line-height-ratio\":\"\",\"box\":{\"x\":20,\"y\":56,\"width\":164.1,\"height\":20.5},\"format\":{\"base\":\"\",\"type\":\"\"},\"value\":\"\",\"ref-id\":\"\",\"overflow\":\"\",\"word-wrap\":\"break-word\",\"svg\":{\"tag\":\"text\",\"attrs\":{\"x\":20,\"y\":72,\"xml:space\":\"preserve\",\"kerning\":\"auto\",\"letter-spacing\":\"normal\",\"id\":\"goog_45091350\",\"fill\":\"#ff0000\",\"fill-opacity\":\"1\",\"font-size\":\"18\",\"font-family\":\"IPAMincho\",\"font-weight\":\"normal\",\"font-style\":\"normal\",\"text-anchor\":\"start\",\"text-decoration\":\"none\"}}}SHAPE--><!--LAYOUT<g xmlns=\"http://www.w3.org/2000/svg\" class=\"s-tblock\" x-format-type=\"\" x-value=\"\" x-format-base=\"\" x-ref-id=\"\" kerning=\"auto\" letter-spacing=\"normal\" x-display=\"true\" x-multiple=\"false\" id=\"goog_45091350\" x-id=\"text\" fill=\"#ff0000\" fill-opacity=\"1\" font-size=\"18\" font-family=\"IPAMincho\" font-weight=\"normal\" font-style=\"normal\" text-anchor=\"start\" text-decoration=\"none\" x-width=\"164.1\" x-height=\"20.5\" x-left=\"20\" x-top=\"56\"><rect class=\"s-tblock-box\" stroke=\"none\" fill=\"#0096fd\" fill-opacity=\"0.2\" width=\"164.1\" height=\"20.5\" x=\"20\" y=\"56\"/><text class=\"s-tblock-id\" font-size=\"10.5\" font-family=\"Helvetica\" font-weight=\"normal\" font-style=\"normal\" text-decoration=\"none\" text-anchor=\"start\" kerning=\"auto\" stroke=\"none\" fill=\"#0096fd\" fill-opacity=\"1\" x=\"24\" y=\"67\">text</text></g>LAYOUT--></g></svg>","state":{"layout-guide":[]}}
\ No newline at end of file

これは辛い。主な原因は以下の通りです。

  1. 改行が取り除かれ、一行になっている
  2. レイアウトデザインは SVG がそのまま保存されている

(2) について少し説明が必要だと思います。.tlf の中身は JSON ではありますが、図形の位置やスタイルなどのデザインの状態は、SVG 形式で文字列として保存されています。下記は古い形式の .tlf の中身をわかりやすく整形したものですが、"svg" キーの値がそれです。

{
  "version": "0.8.2",
  "config": {
    "title": "Report Title",
    "page": {
      "paper-type": "A4",
      "orientation": "landscape",
      "margin-top": 0.0,
      "margin-bottom": 0.0,
      "margin-left": 0.0,
      "margin-right": 0.0,
    }
  },
  "svg": "<svg><g class=\"canvas\"><!--LAYOUT<rect x-id=\"rect_id\" x=\"100.0\" y=\"100.0\" width=\"200\" height=\"200\"/>--><!--SHAPE{ "type": "s-text", "id": "rect_id", "display": "true", "svg": { "attrs": {...} }}SHAPE--></g></svg>"
} 

当然、文字色の情報も "svg" キーの中のどこかに記録されていますが、単純な diff でその変更箇所を探すことは現実的ではありませんでした。

新しい形式

一方、新しい形式で diff を取ると以下のようになります。

--- a/test.tlf
+++ b/test.tlf
@@ -41,7 +41,7 @@
           "IPAMincho"
         ],
         "font-size": 18,
-        "color": "#000000",
+        "color": "#ff0000",
         "text-align": "left",
         "vertical-align": "top",
         "line-height": "",

一目瞭然。

新しい形式の詳細は下記で詳しく説明しています。興味がある方はどうぞ。

Diff が取れて何が嬉しいのか

いろいろあると思いますが、

  • git などの VCS で tlf ファイルの変更履歴が管理しやすくなる
  • Pull Request 等、コードレビューが捗る

などでしょうか。コードレビューが捗る、というか可能になったことは個人的にも本当に助かります。

まとめ

新しい形式に変更した理由は他にもありますが、その一つとして「Diff 辛い問題の解消」について説明しました。 今回の変更によって、Thinreports を使った開発が少しでもやりやすくなれば幸いです。