特定のアプリケーション起動時にスクリーンセーバー、自動サスペンドを止める
概要
タイトル通り。 ちなみに、GNOME3なら拡張機能にCaffeineというのがあって、アプリケーション名を設定することで早々に目的を達成できます。
一定時間無操作でブランクスクリーンにするようなアイドル設定をするのはよくあることかと思いますが、アニメを見たりしてるときはこれが邪魔になることもあります。
しかし、YouTubeを見たりAmazon Prime Videoを見てる時はこういったことには(多分)悩まされないはずです。 同様にVLCのようなメディアプレイヤーで動画を見ているときにも、勝手に画面が消えたりはしないはずです。 これはアプリケーションが裏側でよしなにやってくれているからです。
ただ、必ずしも無操作の時間が長くなるとは考えられないもの(というかリモートデスクトップのクライアント)ではそういったことはやってくれないので、リモートに接続しながら何かを流し見していると問題になることがあります。
これでは満足にコンテンツの消化もできないので、解決法を調べました。 思ったよりも意外に簡単にできて、個人的にちょっと面白かったので記事にします。
方法
gnome-session-inhibit というのがあって、
$ gnome-session-inhibit [実行したいコマンド]
とすれば、指定したコマンドが終了するまでアイドル状態にならなくなります。
自分の場合はリモートデスクトップクライアントにRemminaを使っていて、起動中は勝手に消えてほしくなかったので、起動コマンドを gnome-session-inhibit
でくるんだ カスタム .desktop ファイルを作成してそちらを使うようにしました。
gnome-session-inhibit の裏側
gnome-session-inhibit
は何をしているのかというと、D-Bus 経由で gnome-session の Inhibitor API を呼び出しています。
dbus-monitor
を使って覗き見てみると、gnome-session-inhibit
を使った時に以下のような出力が確認できます。
method call time=1539871252.007783 sender=:1.864 -> destination=org.gnome.SessionManager serial=2 path=/org/gnome/SessionManager; interface=org.gnome.SessionManager; member=Inhibit string "unknown" uint32 0 string "not specified" uint32 8 method return time=1539871252.010514 sender=:1.688 -> destination=:1.864 serial=720 reply_serial=2 uint32 1663795501
このように Inhibit を呼び出すことで、gnome-session にアイドル状態を抑制するよう設定を行っています。
解除時は Uninhibit というのがあって、これを呼び出すことになっているのですが、gnome-session-inhibit
ではこれを特に呼んでいなさそうので、何か別の方法で解除しているようです。
(D-Bus よく知らないのですが、nameLost というsignal が発生しているので、多分通信に使ったバスが消えることで通知している?)
ちなみに Cinnamon のアプレットに Inhibit Applet というのがあって、こちらでも Inhibitor APIを呼び出してアイドル状態を抑制することができます。 Inhibit Applet では状態を変化させるごとに Inihibit と Uninhibit が呼ばれるので、D-Bus を見る分にはこちらの方がわかりやすいかも。
なので、もしアイドル状態を抑制したいようなアプリケーションを作成した時は、どこかで Inhibitor API を叩いてあげると幸せになれそうです。
番外編 systemd-inhibit
他に似たものにsystemd-inhibit
というコマンドもあります。
gnome-session の Inhibitor API と似た形で、systemd にも同様のインタフェースが存在しています。
systemd 側の話なので、スクリーンセーバー抑制等はできないようですが、サスペンド抑制等には使えそうです。
gnome-session 以外のセッション・電源管理アプリケーションで同様の枠組みがあるのかは知りませんが、最悪 systemd 側から今回のようなアプローチも取れそうというメモです。
VS Code と Markdown で資料作成
VS Code 関係の記事ばっかり書いている気がする。
皆さん Markdown 書いてますか?
GitHub の README とか Qiita とか、この記事を読むようなコンピュータ関係の技術者だと比較的使っている人も多いと思います。 それなりに単純な記法でありながら、簡単な文書程度であれば作成できる十分な表現力も持ち合わせていて、Markdown に対応したサービスであれば体裁を整えて出力もしてくれます。
僕ははてなブログに記事を投稿するときはいつも Markdown で書いてますし、そうでなくても大抵の文書はまず Markdown で書いています。 (最近は個人的なメモはScrapboxに書くので若干機会は減りましたが。)
VS Code には現在デフォルトで Markdown をサポートしていて、シンタックスハイライトやプレビューなどの機能を使うことができます。 これだけでも十分便利なのですが、ちょっと変更を加えるだけで更に便利になったので記事にします。
Markdown PDF の導入と設定
まず、Markdown PDFという拡張機能を導入します。 これを導入することで、作成した Markdown の文書を PDF に変換して保存することができるようになります。
設定は以下のようにしました。
{ // 保存時に自動で変換 "markdown-pdf.convertOnSave": true, // pdf出力時の余白設定 "markdown-pdf.margin.bottom": "2.5cm", "markdown-pdf.margin.top": "2.5cm", "markdown-pdf.margin.left": "2.5cm", "markdown-pdf.margin.right": "2.5cm", // PDF変換に用いる 後述 "markdown-pdf.executablePath": "/opt/google/chrome/chrome", // ヘッダとフッタの設定 "markdown-pdf.headerTemplate": "<div />", "markdown-pdf.footerTemplate": "<div style=\"font-size: 9px; margin: 0.5cm auto; \"> <span class='pageNumber'></span> / <span class='totalPages'></span></div>", // ユーザーCSS 後述 "markdown-pdf.styles": ["~/.css/simple-doc.css"], }
概ねコメントと設定名そのままなので特に書くこともないですが、少し注釈。
markdown-pdf.executablePath
ですが、通常は拡張機能側で勝手に導入してくれるはずなので、何も指定しなくても大丈夫なはずです。
ただ、手元の Arch Linux 環境ではうまく動作しなかったので、自分で設定しています。
ユーザーCSS
デフォルトでも結構いい感じに体裁を整えてくれるのですが、見出し周りが少し納得いかなかったのと、段落の始めに空白を確保してほしかったので少し変更を加えました。
CSSを書くことで変更できて、これは上記設定のmarkdown-pdf-styles
の部分に当たります。
今回は、下記CSSを~/.css/simple-doc.css
に配置しました。
margin-bottom
をコメントアウトしているのは、1行目に日付と著者を入れるときにはマージンが無いほうが綺麗に見えて、いきなり本文のときはマージンを入れたほうが綺麗に見えるという理由で結構適当です。
サンプル
この記事自体も Markdown なので、(日付と著者いれて)変換してやると大体こんな感じの出力が得られます。
日付と著者は<div style="text-align: right;">2018/08/31 yk_marble 著</div>
でいれていて、これはもう少し頑張って賢くやってほしい。
ただ、いままでこういうそのまま印刷可能な文章書こうとすると結局 LaTeX 書くことになったりしてたので、個人的には大進歩。
Rust入門した + sync::Mutex.try_lock()とRAII
前々から気になっていたRustをちょっと使ってみることにしました。 Rust は C や C++ といったシステムプログラミングに用いられる言語を置き換えることを目標の1つに掲げているプログラミング言語で、 C++ 並に言語レベルできめ細かい制御、最適化が可能な一方で、後発の言語という利点を活かして、パターンマッチなどの流行りの概念も取り入れつつ、言語自体の構造もすっきりしている、というのが入門前の印象です。
加えて Rust といえば所有権という概念が導入されていることが有名です。 所有権を導入することで並行プログラミングにおいても安全性を文法レベルで保証することができるのですが、これが学習の上で鬼門になっているという噂もよく聞きます。
インストール
2018年8月28日時点では、Rust のインストールは端末で以下のコマンドを実行することが推奨されています。
$ curl https://sh.rustup.rs -sSf | sh
このコマンドは rustup と呼ばれる Rust のツールチェーンを管理するプログラムを導入して、 Rust ツールチェーンを導入するようになっています。 自分の場合、手元の Arch Linux ではリポジトリに rustup が存在していたので、パッケージ管理システムから導入しました。
ただ、ここでいきなり少しハマって、おそらく curl を使って rustup を導入すると一緒に Rust のツールチェーンを導入してくれるのですが、 パッケージ管理システムから rustup をインストールした場合、以下のように手動でツールチェーンを導入する必要がありました。
$ rustup install stable $ rustup default stable
これで Rust のコンパイラ rustc とビルドシステム cargo が使えるようになります。
開発環境
開発環境として Visual Studio Code に Rust プラグイン(Rust (rls) )を導入して用いています。
プラグインの依存を解決するため以下のコマンドを実行します。
(公式ドキュメントでは勝手に導入してくれるような記述だったけれど、自分の環境では導入されなかった?)
$ rustup component add rust-analysis $ rustup component add rls-preview $ rustup component add rust-src
この状態でプロジェクトルートを開くとよしなにやってくれます。
チュートリアル
公式ドキュメントでは プログラミング言語 Rust が勧められています。 第1版と第2版が存在していて、それぞれ日本語への翻訳プロジェクト( 第1版、第2版 )も存在しています。
この記事を書くために調べて初めて複数版存在していることを知って、自分は第1版の1.6を読んでいました。
( rust tutorial でググると日本語の記事はそれがトップだった)
ThreadとMutex
この記事の実質本題です。 第1版1.6のチュートリアルには、かの有名な食事する哲学者問題が例題として含まれています。 解法としてリソースに順序を導入し、必ず小さいリソースから確保を行う方法が提示されています。
これを各哲学者が、フォークが取れなかった場合には一旦手放して、ランダムな時間待ってからフォークを取り直すというものに変更することを考えます。 最初は誤ってこんなコードを書いていました。
fn eat(&self, table: &Table) { loop { thread::sleep(Duration::from_millis(rand::thread_rng().gen_range(10, 100))); let left_lock = match table.forks[self.left_id].try_lock() { Ok(_) => (), Err(_) => continue, }; let right_lock = match table.forks[self.right_id].try_lock() { Ok(_) => (), Err(_) => continue, }; println!("{} is eating. ({}, {})", self.name, self.left_id, self.right_id); thread::sleep(Duration::from_millis(1000)); println!("{} is done eating.", self.name); break; } }
実行してみると分かりますが、全然排他制御できていない結果が得られます。
これはロックの解法タイミングが意図したものと異なっているために起こります。 Rust の Mutex には RAII と呼ばれる仕組みが用いられています。 RAII では変数が初期化された時にロックを取得し、変数が破棄されたタイミングでロックを解放します。
つまり、上記コードでは .try_lock()
の返り値が得られた時点でロックが取得されます(もしくはロックが取得できずに Err
が返ってくる)。
次にパターンマッチに入っていますが、ここで .try_lock()
の値を使わずに処理を終えています。
排他制御がしたいだけで実際の中身には興味がないためにこのようなコードになったのですが、結果このパターンマッチを終えた時点で .try_lock()
の返り値はどこにも束縛されていないために破棄されます。
RAII では破棄と共にロックの解法も行われるので、結果としてパターンマッチを終えた時点でロックが解放されていたのです。
この問題を解決するには、.try_lock()
の返り値を実際にロックを確保したい間は適当な変数に束縛しておく必要があります。
解法としては以下のように .try_lock()
の返り値を単純に束縛したり、
let left_lock = table.forks[self.left_id].try_lock(); if let Err(_) = left_lock { continue; } let right_lock = table.forks[self.right_id].try_lock(); if let Err(_) = right_lock { continue; }
パターンマッチで Ok
が保持する値を返すというものが考えられます。
let _left_lock = match table.forks[self.left_id].try_lock() { Ok(mtx) => mtx, Err(_) => continue, }; let _right_lock = match table.forks[self.right_id].try_lock() { Ok(mtx) => mtx, Err(_) => continue, };
RAII を知っていれば別に大した話じゃなくて、ただマヌケなコードを書いているなぁという感想で終わるのですが、あまり C++ 以外で RAII を見たことが無いのも事実で、知らない人は引っかかってつらそうだなぁと思い一応記事にしました。
Vim と VS Code Vim で挿入モードから抜けた時に IME を無効化する (Linux fcitx版)
Vim を使って日本語を書いていると、IMEをONにした状態でノーマルモードで入力してしまって、思い通りに操作できないことがよくあると思います。 挿入モード以外で日本語を入力する必要はおそらく無いと思われるので、挿入モードを抜けるときに強制的にIMEをOFFにすることでこの問題は解決できそうです。
同じことをやっている人は探せば結構出てくるのですが、挿入モードでは常にIMEをONにしたい流派がいたり、OSによって設定が異なったりと、人の数だけ設定がありそうだったので、なるべくミニマルで流用しやすそうな設定を残しておきたくてこの記事を書きました。
IMEが絡む都合上、「これでどの環境でも動く」という楽々設定はできないので、以降はLinuxでIMEとしてfcitxを使用している場合の話になります。 ただし、IME切り替えの部分を各自の環境に置き換えることで読み替えることは可能で、この点については後述します。
Linux 上の Vim での設定
autocmd InsertLeave * call system("fcitx-remote -c")
IMEとしてfcitxを使っていればこの設定だけで挿入モードから抜けたときにIMEをOFFにできます。 WindowsやMac、Linuxでもibusを使っているような環境でも同じvimrcを使いまわしたければ、環境に応じてロードしないようにする設定も加えたほうが無難だと思います。
InsertLeave
とは逆に、挿入モードに入ったときに発火されるイベントInsertEnter
もあるので、挿入モードでは常にIMEをONにしたい派の人はそちらも使えると思います。
VSCode Vim での設定
VSCode Vim でも同様の設定は可能で、settings.json
に以下の設定を書き加えます。
"vim.autoSwitchInputMethod.enable": true, "vim.autoSwitchInputMethod.defaultIM": "-c", "vim.autoSwitchInputMethod.switchIMCmd": "/usr/bin/fcitx-remote {im}",
defaultIM の値が switchIMCmd の {im}
の部分に渡されます。
現時点ではautoSwitchInputMethod
は、挿入モードを抜ける時にdefaultIM
に切り替えるという動作だけになっているようなので、switchIMCmd
に直接書き下しても特に問題はなさそうですが、将来的にIME切り替え機能が追加される可能性を考えると、上述のような設定の方が無難に思えます。
fcitx-remote と他の環境への応用
fcitx-remote
をオプションなしで実行すると現在のIMEの状態に応じて、
が返ってきます。 また、オプションとして、
があります。
今回は現在の状態に関与せずにただ無効化できればいいので、容易に設定できました。 挿入モードの最終状態に応じて処理を分けたい場合(次に挿入モードに入ったときに状態を保存したいなど)、このあたりをうまく使うことで実現できそうです。
逆に、他のIMEを使っていても、fcitx-remote -c
に相当するコマンドがあれば、適宜置き換えることでその環境でも今回の記事と同等の効果が得られるはずです。
Android 8.0 Oreo で setOnClickPendingIntent を用いて PendingIntent を呼び出す際の注意点
概要
Android 開発初心者が Widget 開発しようとしたら PendingIntent が虚空に飲まれて困ったので対処しました。 長々と本文がありますが、最後の余談に書いてあることの方が真理で即解決可能な方法です。
注意
筆者は Android 開発初心者で理解が間違っている、不正確な文章、表現が含まれる可能性があるため話半分で読むことをおすすめします。 誤りの指摘、本当の解決法(?)等がございましたらコメントなり Twitter なりでお伝えいただければ幸いです。
導入と動かないサンプルコード
自宅の照明を制御するためにいちいち Google アシスタントに話しかけるのは、引きこもり大学院生の声帯に対してあまりに過酷すぎる問題があります。 というわけで、照明操作のフロントエンドに Android の Widget を使おうと思ってサクッと作ることにしました。
サクッと、とは言ったものの、今まで Android 開発は GPS ロガーを作るのに触ってみた程度の経験で Widget 開発に関しては全く経験がなかったので爆速で座礁しました。 とりあえず実現したかった機能としては、
という機能で、2.の部分を実現する Service を作成して、Widget をタップするとこの Service を起動するという構成が一般的そうです。 HTTP リクエストは後ほど実装するとして、とりあえず Widget タップ → Service 起動の流れを実装することにすると、以下のようなサンプルがヒットします。
Android 8.0での変更
このサンプル、最初は上手く動いていたのですが、つい先日端末を Android 8.0 Oreo にアップデートしたところ動かなくなってしまいました。 そもそも Service の onCreate( ) すら呼ばれていないようで、無言で生成されることもなく死んでいるようです。 色々調査してみると、どうやら Oreo から Service の起動に制約が増えたようです。
Android 8.0 での動作変更点 | Android Developers
要約すると、
- バックグラウンドで起動される行儀の悪い Service によるバッテリ消費の問題が深刻化している
- Android O では予め許可された特殊な Service を除いてバックグラウンド実行を禁止することに
- 許可されない(通常の) Service を実行する際にはフォアグラウンドとして実行する必要がある
今回の Service も許可されない Service のためどこかで差し止められたのだと考えられます。 (変更を加えずに従来通り Service を呼び出しても、数秒は実行されるっぽいので Service の onCreate( ) すら呼ばれていないのは奇妙ですが……)
この変更点は日本語の記事でもいくつか取り上げている例があったのですが、どれも Activity からバックグラウンド実行する Service を呼び出す話で、startService( ) を書き換えるだけでオッケーみたいなことを書いているのですが、Widget では PendingIntent 経由で呼ばれるのでこの方法は使えず全然オッケーではありません。
対処法
ここから行き詰まってインターネットを彷徨い続けることになったのですが、結論から言うと PendingIntent を作成する際に getService( ) から getForegroundService( )に変更する + Service 側で startForeground( ) を呼び出すことで問題は解決しました。 getForegroundService( ) はAPI Level 26で追加されたようなので、場合によってはビルド設定を見直す必要がありそうです。
余談
差し当たってはビルド設定の targetSdkVersion を25にすることでこの変更を逃れられて、当初のサンプルコードを入力することで動作させることが可能なようです。
リンカの気持ちになってOpenCVのメジャーバージョン変更と戦った話
今年の抱負が "アウトプットを増やす" に決定したので早速(1月も終盤)書きます。
背景
現在、諸事情でUbuntu14.04LTS上で作業をしているのですが、当然のように最新のパッケージは降ってきません。 OpenCVも2.4.8がリポジトリ上の最新となっています。 そんな中で3系で新しく導入された、魚眼レンズのカメラキャリブレーションに関する関数が使いたかったので手元でビルドしました。 この時点でかなり疲弊していて、「prefixが/usr/localになってるけど、今日は取りあえず動くか確認して明日整理すればええやろ、インストールや!」などと思ったのが問題のそもそもの発端でした。 無事、魚眼レンズのキャリブレーションを終え、翌日に面倒事を押しつけることに成功し満足げな表情でこの日は作業を終えました。
翌日、また別の作業でビルドを走らせると何やらエラーがでています。 見たところOpenCV周りのエラーっぽかったので心当たりありまくりで、取りあえず3系のOpenCVを消してビルドし直しました。 しかし、エラーは消えません。undefined reference だそうです。 こういった類のエラーが出る時は、大抵ライブラリの指定忘れなのですが、cmakeを使ってこれまで同じようにビルドしてきたのに、今さらこんなエラーが出るのも変な話です。 そんな阿呆なと思いながら、コンパイルコマンドをダンプさせてじっくり眺めてみても、やっぱりきちんと2系のライブラリが漏れなく指定されています。 更に気になる点として、OpenCVに関係する関数が全体的にundefined referenceならよくある話なのですが、今回はごく一部だけがシンボル解決に失敗しているようでした。
ライブラリに含まれるシンボルの調査
というわけで、本当に指定したライブラリに今探しているシンボルがないのか、逆にどんなシンボルならあるのか(型が合ってない可能性)を調べることにしました。
readelf
コマンドを使うことで、実行ファイルやライブラリなどのELFバイナリのメタデータを人間が読みやすい(?)形で得ることができます。
シンボルを出力するためにオプション-s
を、途中で出力が打ち切られるのを防ぐために-W
を指定して、
readelf -s -W <ELFファイル>
とすると、何やら面妖な記号を含みながらそれっぽいのがズラズラっと出力されると思います。*1
(前略) _ZN2cv12findContoursERKNS_17_OutputArrayERKNS_12_OutputArrayEiiNS_6Point_IiEE
この記号はC++の名前空間や多重定義を実現しながらユニークなシンボル名を作り出すために付加されていて、c++filt
などのコマンドを用いることで以下のようにデコードすることができます。
(前略) cv::findContours(cv::_OutputArray const&, cv::_OutputArray const&, int, int, cv::Point_<int>)
問題解決
さて、実はさっきから出てきているこのcv::findContours
というのが、今回名前解決がされないと言われている関数の1つでした。
見たところ普通に存在しているようですが……エラー文と比較してみましょう。
undefined reference to `cv::findContours(cv::_InputOutputArray const&, cv::_OutputArray const&, cv::_OutputArray const&, int, int, cv::Point_<int>)'
第一引数がなんか微妙に違う……。 調べてみると、OpenCV2系までは _InputOutputArray は _OutputArray の別名だったそうなのですが、3系でそれぞれ別の構造体となったようです。 つまり、ライブラリは2系なので _InputOutputArray は別名定義の解決が行われた _OutputArray としてシンボルが存在しており、リンクしようとしているバイナリの方は3系の残滓が残っている時にコンパイルしたために、独立した _InputOutputArrayとしてシンボルを登録していたようです。 というわけで、一旦バイナリを全てコンパイルし直したらすんなり問題解決しました。
結論
市原仁奈「リンカの気持ちになるですよー!」
*1:readelfも実は3系の結果で、第一引数の部分だけ話の都合で書き換えてあります。手元に2系のバイナリがなかったので……。
心から温まる技術
この記事はcoins Advent Calendar 2016 19日目の記事です。
毎日毎日寒いですね。今日は寒い日々を耐え忍ぶための技術についてです。
coinsで暖を取ると言えば、一般的には計算機をぶん回すことを指すと思います。 徐ろにGentoo Linuxのインストールを始めたり、撮りためたアニメをエンコードするのは冬の恒例行事とも言えるでしょう。 過去のcoins Advent calendarを眺めてみてもこたつをサーバで温める記事があったり、計算機で暖を取ることはもはや目新しいことでもありません。 そこで、今日はちょっと趣向を変えてみようと思います。
迫りくるクリスマス。 どんなに部屋が暖かくても共に過ごす相手がいなければ、心は冷えきったままで生命の温もりが恋しくなるでしょう。 無ければ作るの精神ということで、今日はちょちょっと生命を作り出してしまいましょう。 coinsで生命と言えば?
そう、ライフゲームですね。
ご存じない方のために説明すると、ライフゲームとは碁盤の目状に並んだセルの状態が簡単なルールに従って更新されていく、シミュレーションゲームの一種です。 各々のセルは生死どちらかの状態で、
- 死んだセルの周囲8セルの内、生きているセルがちょうど3つの時にそのセルは「生」に変わる
- 生きたセルの周囲8セルの内、生きているセルが1つ以下の時にそのセルは死ぬ
- 生きたセルの周囲8セルの内、生きているセルが4つ以上の時にそのセルは死ぬ
- 上記以外の場合、セルの状態は変化しない
の4つのルールに従ってセルの更新を繰り返し、盤面の変化を眺めて楽しみます。
というわけで、ライフゲームを作っていきます。 ただ単に盤面の状態を液晶ディスプレイに表示するだけだと生命の温もり感に欠けるので、Arduinoを使って8x8のマトリクスLEDに表示しようと思います。 マトリクスLED内部は、
(MNA20SR092Gデータシートより引用)
こんな回路をしているので、何も考えずにつなごうとするとArduino Unoではピン数が足りず、金を積んでArduino Megaを買う必要が出てきます。 そんなお金は無いので、今回は8bitのシリパラ変換ICを2つ使ってつなぐことでArduino Unoからでも制御できるようにします。 いい感じにつないだ結果こうなります。
後はパパっとArduinoのスケッチを書いてしまいましょう。 マトリクスLEDの点灯には少し工夫が必要で、そのままだと一度に全てのLEDを任意の点灯状態にすることができません。 各行(もしくは列)1つだけに着目すると任意の点灯状態にすることができるので、まず1行だけ表示して、すぐに次の1行だけを表示して……と表示する行を高速で切り替えることで全てのLEDについて任意の点灯状態にすることができます。 ArduinoにはShiftOutという1byteのデータをシリアル出力してくれる便利な関数があるので、これを使うと各行のデータを簡単にシリアル出力することができます。 最終的なスケッチがこちらです。
このスケッチをArduinoに書き込んで完成です。
どうですか、生命の温もりを感じられますか。 僕は部屋のあまりの寒さに凍え死にそうです。 とはいえ、もともと箱庭シミュレーションを無心で眺めているのが好きなので楽しいことは楽しいです。
完全にただの一発ネタでしたが、ここまでお読みいただきありがとうございました。