memo

思いついたこと、やってみたことをテキトウに残していく。

HEAD /

name last modified size description
archives/ 2020-09-21 19:18  
codes/ 2011-04-22 02:17  
images/ 2015-06-18 01:22  
patches/ 2015-04-07 00:11  
tags/ 2016-06-25 11:45  

HEAD / archives / 2020-09 / 2020-09-21.rst

Wayland 環境でマウスのボタンをキー入力にマッピングする方法

Wayland (libinput?) な環境で、マウスのボタンをクリックした時に、任意のキー入力がされたことにしたい。 hwdb の設定を上書きすればできそう。

例えば、自分の使っているマウスには「戻る」「進む」ボタンがあるが、これをそれぞれ "Home", "End" キーという事にする場合。

  1. /proc/bus/input/devices を眺めて、マウスの名前と(現在の) event 番号を調べる:

    ...
    N: Name="Logitech MX Ergo"
    ...
    H: Handlers=sysrq kbd mouse0 event1 leds
    ...
    

    ここでは名前は Logitech MX Ergo で、 event 番号は 1

  2. evtest コマンドで、マウスのボタンに対応するコード(?)を調べる:

    $ sudo evtest /dev/input/event1
    ...
    Testing ... (interrupt to exit)
    Event: time 1600678269.823814, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90004
    Event: time 1600678269.823814, type 1 (EV_KEY), code 275 (BTN_SIDE), value 1
    Event: time 1600678269.953810, -------------- SYN_REPORT ------------
    Event: time 1600678272.895815, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90005
    Event: time 1600678272.895815, type 1 (EV_KEY), code 276 (BTN_EXTRA), value 1
    Event: time 1600678272.895815, -------------- SYN_REPORT ------------
    

    「戻る」ボタンが 90004, 「進む」ボタンが 90005

  3. /etc/udev/hwdb.d/99-local.hwdb をこんな感じに書く:

    evdev:name:Logitech MX Ergo:*
      KEYBOARD_KEY_90004=end
      KEYBOARD_KEY_90005=home
    
  4. hwdb の更新 & 再読込:

    $ sudo systemd-hwdb update && sudo udevadm trigger /dev/input/event1
    

HEAD / archives / 2019-06 / 2019-06-23.rst

ipython で twisted の deferred を await する

気付いてなかったけど、 ipython で await できるようになっていた

ドキュメントを読むに、なんか関数書けば対応を足せそうだったので、 twisted の deferred を await できるようにしてみる。

twisted は asyncio と違って、一度 reactor を動かしてしまうと止められなくなってしまうので、そのへんいい感じにやってくれる crochet を利用しつつ、以下のようなコードを置いておく。

$HOME/.ipython/profile_default/startup/99-twisted_runner.py:

try:
    import crochet

    from twisted.internet import defer

except ImportError:
    pass

else:
    def twisted_runner(deferred):
        crochet.setup()
        result = crochet.run_in_reactor(
            lambda: defer.ensureDeferred(deferred))()

        return result.wait(2**31)

後は %autoawait twisted_runner で有効にする。

こんな感じ:

In [1]: %autoawait twisted_runner

In [2]: import treq

In [3]: r = await treq.get('http://localhost')

In [4]: data = await r.text()

In [5]: print(data)
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

HEAD / archives / 2018-12 / 2018-12-26.rst

terminal でカーソルを動かす方法

よくある手法として \r でカーソルを行頭に戻すというのがあって、例えば以下のような感じにして進捗表示に棒を回したりするけど:

$ spinner=('-' '\' '|' '/')
$ for i in {1..100}; do
> echo -n "${spinner[$(($i % 4))]}"
> echo -ne '\r'
> sleep 0.1
> done

しかし、じゃあコレを縦に3つ並べて回したい、となった時に \r だとどうしょうもないので、カーソルをもっと自由に動かす方法。

端末ごとに適切なエスケープシーケンスを吐いてやればカーソルを動かせる。 何が適切かは terminfo データベースに書いてある。

シェルからは tput コマンドを使って名前から対応するものを出力できる。 ということで、縦に3つ並べるのはこうなる:

$ spinner=('-' '\' '|' '/')
$ for i in {1..100}; do
> echo "${spinner[$(($i % 4))]}"
> echo "${spinner[$(($i % 4))]}"
> echo "${spinner[$(($i % 4))]}"
> tput cuu 3
> sleep 0.1
> done

(tput cuu 3 でカーソルを3つ上に動かす。その他、何が出来るかは man terminfo を後で読む。)

python だと curses モジュールで同様のことが出来る:

import curses
import sys
import time


SPINNER = list(r'-\|/')

curses.setupterm()

for i in range(100):
    print(SPINNER[i % 4])
    print(SPINNER[i % 4])
    print(SPINNER[i % 4])

    sys.stdout.buffer.write(curses.tparm(curses.tigetstr('cuu'), 3))
    sys.stdout.buffer.flush()
    time.sleep(0.1)

HEAD / archives / 2018-09 / 2018-09-08.rst

ipython で jedi での補完を有効にする方法

ipython は 6.0 から jedi での補完が出来るようになったのだけど、なんかその後に問題が見つかったとかでデフォルト無効になっている。 それを有効にする方法。

以下のような設定を置けば良い。

~/.ipython/profile_<name>/ipython_config.py:

c = get_config()
c.Completer.use_jedi = True

HEAD / archives / 2018-07 / 2018-07-30.rst

flatpak を使って python application をパッケージングしてみる試み、その3

前回 は lxml のビルドにコケるのでしょうがなく自前で runtime を用意するということをしたのだけど、 現在は wheel が提供されるようになったので、そのような頑張りは不要となった。

ということで このような yaml を用意した上で、 flatpak-builder を実行すれば普通にアプリケーションがビルドできる:

$ flatpak-builder ../konnyaku-build org.u7fa9.konnyaku.yaml

ビルド後に --run オプションを付けて再実行すると、アプリケーションの動作確認が出来る:

$ flatpak-builder --run ../konnyaku-build org.u7fa9.konnyaku.yaml konnyaku

問題無さそうなら --repo を指定して、ビルド結果を repository に書き出す:

$ flatpak-builder --force-clean --repo=~/repos/flatpak/u7fa9.org ../konnyaku-build org.u7fa9.konnyaku.yaml

HEAD / archives / 2017-09 / 2017-09-23.rst

flatpak で linux 版 steam client を動かす

linux 版の steam client は、 ubuntu 用のバイナリしか無い(らしい)とか、 32bit 版のライブラリが要求される(らしい)とかで、特にちょっとマイナーなディストリにインストールするのはクソめんどくさい(らしい)。

ということで、 flatpak を使って動かす。

steam client は flathub にあるので、まずはこの repository を追加する:

$ flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo

そしたら steam client をインストール:

$ flatpak --user install flathub com.valvesoftware.Steam

で、実行:

$ flatpak run com.valvesoftware.Steam

HEAD / archives / 2017-05 / 2017-05-20_1.rst

rust で WSGI サーバーを書いてみる

cpython crate は rust から python インタプリタを呼び出すことにも使うことができる。

ということで、試しに雑 WSGI サーバーを書いてみた。

https://github.com/nakamuray/wsgi-server

取り敢えずテキトウに用意した django アプリケーションの admin 画面が表示される程度には動いたので、満足。

HEAD / archives / 2017-05 / 2017-05-20.rst

rust で python 拡張モジュールを作る方法

cpython crate を利用することで、 rust で python 拡張モジュールを簡単に書くことができる。

また、 setuptools-rust を利用することで、そうして書かれたモジュールを含む python パッケージを簡単に書くことができる。

ということで、試しにフィボナッチ数を計算する拡張モジュール (関数) を書いてみた。

https://github.com/nakamuray/fib

せっかくなのでマルチスレッドで並列計算させるようにしてみたけど、ちゃんと CPU 400% とか使えてるようなので、満足。

HEAD / archives / 2017-02 / 2017-02-20.rst

flatpak に入れた python アプリケーションの起動がやけに遅い

先日 flatpak に python アプリケーションを入れてみたわけだけど、 flatpak をかぶせることによる起動コストの増加はどんなもんだろうか、 と気になったので、 time を使ってテキトウに計ってみる。

普通に実行した場合:

(venv) $ time konnyaku --help
Usage: konnyaku [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --db-debug / --no-db-debug
  --help                      Show this message and exit.

Commands:
  add      add site
  check    check site updates
  links    list links
  list     list sites
  modify   modify site
  oneshot  add & check but not save
  remove   remove site
  show     show site information
konnyaku --help  0.74s user 0.06s system 99% cpu 0.806 total

0.8秒くらい。

続いて flatpak に入れたヤツ:

$ time flatpak run org.u7fa9.konnyaku --help
Usage: konnyaku [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --db-debug / --no-db-debug
  --help                      Show this message and exit.

Commands:
  add      add site
  check    check site updates
  links    list links
  list     list sites
  modify   modify site
  oneshot  add & check but not save
  remove   remove site
  show     show site information
flatpak run org.u7fa9.konnyaku --help  0.06s user 0.02s system 2% cpu 2.899 total

2.9秒くらい。

思ってた以上に遅い。何だこれ。

調査

ひとまず python ではなくて、明らかに一瞬で実行が終わるはずのコマンドを実行させてみる:

$ time flatpak run --command=/bin/true org.u7fa9.konnyaku
flatpak run --command=/bin/true org.u7fa9.konnyaku  0.06s user 0.02s system 36% cpu 0.229 total

0.2秒くらい。これが flatpak を立ち上げるのに必要なコスト。じゃあさっきの2秒近い差はどっから出てきた?

python アプリケーションで起動が思いのほか遅いというような場合、 .pyc ファイルを作成し忘れたケースが考えられる。 flatpak では runtime/application は read-only で mount されるため、パッケージング時に含めそびれると、実行時には作成できない (作成しても保存できない) ので、毎回 .py ファイルを読み込むことになる。

ファイルがあるか確認してみる。

$ flatpak run --command=/bin/sh org.u7fa9.konnyaku
~ $ ls -l /app/lib/python3.5/site-packages/konnyaku/
total 36
-rwxr--r--  231 nakamuray nakamuray         0 Jan  1  1970 __init__.py
drwxr-xr-x    2 nakamuray nakamuray         8 Jan  1  1970 __pycache__
-rwxr--r--    2 nakamuray nakamuray      6281 Jan  1  1970 cli.py
-rwxr--r--    2 nakamuray nakamuray      1599 Jan  1  1970 concurrentutils.py
-rwxr--r--    2 nakamuray nakamuray        39 Jan  1  1970 exceptions.py
-rwxr--r--    2 nakamuray nakamuray      3287 Jan  1  1970 models.py
-rwxr--r--    2 nakamuray nakamuray      3594 Jan  1  1970 tasks.py
~ $ ls -l /app/lib/python3.5/site-packages/konnyaku/__pycache__/
total 31
-rwxr--r--    2 nakamuray nakamuray       136 Jan  1  1970 __init__.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray      6502 Jan  1  1970 cli.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray      1915 Jan  1  1970 concurrentutils.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray       310 Jan  1  1970 exceptions.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray      3957 Jan  1  1970 models.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray      3004 Jan  1  1970 tasks.cpython-35.pyc

ちゃんとある...が、なんだこのタイムスタンプは?

原因

どうやら flatpak (が内部で使ってる ostree) はファイルの更新日時等々を保存しないらしく、 そのためそこからファイルを取り出すと 1970-01-01 00:00:00 (UTC), unix time で "0" が更新日時として設定されるらしい。

さて、 python は .pyc ファイル中に元のソースファイルの更新日時を保存していて、 これが現在のソースファイルの日時と異なっている場合はファイル内容に変更があると見なし、 .pyc を読み込まないようになっている。

で、全てのファイルの更新日時が 1970-01-01 00:00:00 に 変更 されてしまったため、 全ての .pyc ファイルでこのチェックに引っかかるようになってしまい、 また先述したように新規で .pyc ファイルを作成することも出来ないので、 結果として毎回 .py ファイルを読み込むようになってしまっているようだ。

その分が起動時間の差に現れていると考えられる。

対策

.pyc ファイル中に記録されている更新日時情報が 1970-01-01 00:00:00 を指すように、データを書き換える。

この情報は 4-7 bytes の範囲に書かれているようなので、 dd を使ってゼロ埋めしてやればよい。

ということで、前回のパッケージングスクリプトに以下のような変更を加えて作り直す:

diff -r 43abc9291d3b flatpak/build-konnyaku-runtime.bash
--- a/flatpak/build-konnyaku-runtime.bash       Mon Feb 20 15:21:54 2017 +0900
+++ b/flatpak/build-konnyaku-runtime.bash       Mon Feb 20 21:23:24 2017 +0900
@@ -53,6 +53,10 @@
 run_in_rootfs apk update
 run_in_rootfs apk add "${RUNTIME_PACKAGES[@]}" || true

+# XXX: fix .pyc's timestamp information to match "1970-01-01 00:00:00",
+#      files checked out from ostree will be.
+run_in_rootfs find /usr/lib/python3.5 -name "*.pyc" -exec dd if=/dev/zero of="{}" bs=4 seek=1 count=1 conv=notrunc ";" 2>/dev/null
+
 # create sdk based on runtime
 cp -a "${rootfs_dir}" "${sdk_dir}"/

diff -r 43abc9291d3b flatpak/build-konnyaku.bash
--- a/flatpak/build-konnyaku.bash       Mon Feb 20 15:21:54 2017 +0900
+++ b/flatpak/build-konnyaku.bash       Mon Feb 20 21:23:24 2017 +0900
@@ -22,5 +22,10 @@
 flatpak build "${build_location}" python3 -m venv /app
 flatpak build --share=network "${build_location}" /app/bin/pip install -U pip
 flatpak build --share=network "${build_location}" /app/bin/pip install "${URL}"
+
+# XXX: fix .pyc's timestamp information to match "1970-01-01 00:00:00",
+#      files checked out from ostree will be.
+flatpak build "${build_location}" find /app -name "*.pyc" -exec dd if=/dev/zero of="{}" bs=4 seek=1 count=1 conv=notrunc ";" 2>/dev/null
+
 flatpak build-finish "${BUILD_FINISH_ARGS[@]}" "${build_location}"
 flatpak build-export "${REPOSITORY}" "${build_location}" "${VERSION}"

結果

$ time flatpak run org.u7fa9.konnyaku --help
Usage: konnyaku [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --db-debug / --no-db-debug
  --help                      Show this message and exit.

Commands:
  add      add site
  check    check site updates
  links    list links
  list     list sites
  modify   modify site
  oneshot  add & check but not save
  remove   remove site
  show     show site information
flatpak run org.u7fa9.konnyaku --help  0.06s user 0.02s system 7% cpu 1.139 total

直接実行した場合の0.8秒と比べて、0.3秒くらいの差。 true を実行した時の0.2秒にかなり近くなった。

余談

flatpak-builder にはコレと同様の対応処理が入っているようなので、 コイツで python アプリケーションが普通にパッケージングできるようになれば、こんなこと気にしなくてよくなるのかも。

HEAD / archives / 2017-02 / 2017-02-10.rst

Jinja2 の async support を試した

Jinja2 にバージョン 2.9 で async support が入ったとのこと。

  • Environment 作成時に引数 enable_async=True を渡すことで有効になる
  • テンプレート中の関数呼び出しが、 (結果が coroutine であれば?) 自動で await されるようになる
  • Template.render_async() を呼ぶことで、テンプレートをレンダリングする coroutine が得られる

ということで動かしてみる:

import asyncio
import jinja2


# テキトウに非同期な関数を用意する
async def do_something(value: int) -> int:
    await asyncio.sleep(1)
    return value + 1


async def main():
    # `enable_async=True` を渡して Environment 作成
    env = jinja2.Environment(enable_async=True)
    env.globals.update({
        'do_something': do_something,
    })
    tmpl = env.from_string('''
    {#- テンプレート中では、非同期関数は普通の関数と同じように呼び出せる #}
    result: {{ do_something(1) }}
    ''')

    # .render_async() で coroutine が得られる
    coro = tmpl.render_async()
    print(repr(coro))
    # coroutine を await することでレンダリング結果が手に入る
    txt = await coro
    print(txt)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

実行結果:

<coroutine object Template.render_async at 0x7ff2038688e0>

    result: 2

さて、ドキュメントには asyncio がどうのこうのと書いてあるが、 これは Template.render() などの同期的なメソッドが呼ばれた際に、 内部で asyncio の event loop を取得して coroutine を実行し、結果を得ているという話っぽい。

なので、 .render_async() などを呼んでいる分には、 asyncio 以外の event loop でも使えそう。

ということで twisted と組み合わせて動かしてみる:

import jinja2

from twisted.internet import defer


def sleep(delay: int) -> defer.Deferred:
    d = defer.Deferred()

    from twisted.internet import reactor
    reactor.callLater(delay, d.callback, None)

    return d


# テキトウに非同期な関数を用意する
async def do_something(value: int) -> int:
    await sleep(1)
    return value + 1


# Deferred を返す版も試しておく
def do_something_deferred(value: int) -> defer.Deferred:
    d = sleep(1)
    d.addCallback(lambda _: value + 1)
    return d


async def main():
    # `enable_async=True` を渡して Environment 作成
    env = jinja2.Environment(enable_async=True)
    env.globals.update({
        'do_something': do_something,
        'do_something_deferred': do_something_deferred,
    })
    tmpl = env.from_string('''
    {#- テンプレート中では、非同期関数は普通の関数と同じように呼び出せる #}
    result: {{ do_something(1) }}
    {#- Deferred を返す関数も同様に、普通に呼び出せる #}
    result from deferred: {{ do_something_deferred(1) }}
    ''')

    # .render_async() で coroutine が得られる
    coro = tmpl.render_async()
    print(repr(coro))
    # coroutine を await することでレンダリング結果が手に入る
    txt = await coro
    print(txt)


if __name__ == '__main__':
    from twisted.internet import task
    task.react(lambda _: defer.ensureDeferred(main()))

実行結果:

<coroutine object Template.render_async at 0x7f16bbea5ca8>

    result: 2
    result from deferred: 2
powered by blikit