dothikoのカクカクワールド2D REBOOT

プログラミングとかLinux関連(特にOSSのグラフィックツール関連)とかレトロゲームとか色々。

Xubuntu 16.04でCintiq 13HDを自動設定するの巻

ついつい、勢いでwacomの液タブCintiq 13HDを買ってしまいました(^^; 旧モデルなのは、安いだけでなくLinuxで使うには何も考えなくて良くて都合が良いというメリットもあるのです。 f:id:dothiko:20171027203651j:plain どーでもよいのですがこの絵、tegaki_dtで頑張って書いてたらボタンの誤爆で「画像が動く」状態になっちゃってどうにもならず、後で描き直してツイートしたのだけどやっぱ液タブなのでそのへんも速かったですな…

いやそれにしても中華液タブが割高に思えてしまう値段ですなコレは… これで一応最高レベル(あくまで廉価帯内での比較)の液タブを経験しておけば、後々中華品に手を出した時に 「ワコムだったらこの不快な現象は起きないのだろうか」という疑問などを一掃してくれるわけでまぁ買ってよかったかなと!

表題については、正直、現在のUbuntuLinuxであれば挿しただけでドライバが入って認識されるので何のネタにもならないのですが、デュアルモニタ(xinerama)だと横に超長いor縦に2つのモニタになっているので、全画面にマップされてしまいます。

これを以前、板タブについても避けようと思っていろいろやった挙句、面倒なので起動時に設定して挿しっぱということになりましたが、液タブはなんか光って電気もったいなさげなので使わない時は出来る限り消したいのですな。

というわけで再挑戦なのでありました。

まずは

  • udevにrulesファイルを書いて、RUN+=で一段目のシェルスクリプト実行。
  • 二段目のシェルスクリプトを実行、二段目でsleep 3ぐらいして、実際にX11にペンタブデバイスが認識されるのを待つ
  • 二段目スクリプトではsleepの後、export XAUTHORITY=/home/ユーザー/.Xauthority
  • あと、export DISPLAY=:0
  • んで、xsetwacom。

rulesファイルは/etc/udev/rules.d/99-cintiq.rulesとでもして

SUBSYSTEM!="input",ACTION!="add",GOTO="my_cintiq_end"
ENV{ID_MODEL}=="Wacom_Cintiq_13HD",ENV{ID_TYPE}=="hid",RUN+="/usr/local/lib/tablet/cintiq_setup_launcher.sh"
LABEL="my_cintiq_end"

という方法を取りました。まぁ試行錯誤の結果これで動くことは動く。…のですが、何度も呼び出されている模様… 実に気持ち悪いのでフラッグファイルを立てるなど試行錯誤してみたのですが、うまく行かない。何故か逆に設定がされなくなったりして…

そこで、考え方を変えて、udevとX11の組み合わせは筋が悪いのでとりあえずやめるとして 以前見失った「XFCE4の設定でペンタブの自動適用設定があったはず、あれをもう一度探そう」となりました。

以前はたしか「マウスとタッチパッド」にあったのですが、今は無い… んでようやく見つけましたよ、「リムーバブルとメディア」の中にその項目があった。 そんなん分かるか? f:id:dothiko:20171027203723j:plain これに、こんなふうに

/usr/local/bin/xfce4_detect_pentab.py --node %d

というように%dをつけると「/dev/input/event22」とか「/dev/input/mouse5」とかのデバイスノードを引数で送りつけてその都度呼んでくれます。だがしかし「その都度」なので、ペンタブ一つさすと3回ぐらいは呼ばれるというわけです。

それでもってデバイスノードが得られても、何のペンタブが挿されたのかは即座にはわかりません…

まぁ結局長くなるのでここには書きませんが、

  • cat /proc/bus/input/devices すると、その中に H: Handlers=mouse5 event22 みたいな情報があり、また、N: Name="Wacom Intuos 6x8 Pen"みたいなxsetwacomデバイス名も得られる。
  • pythonでこれを解析してデバイスノードからデバイス名を得るようなスクリプトを書き
  • ペンタブに対する設定はjsonファイル(config.json)にまとめて記述。
  • さらにsubprocessでxsetwacomを呼び出して、先ほどのjsonに書かれた設定を反映。
  • 一気にすべての設定を適用してしまう。xfce4のdaemonはおそらくdbusで捕捉してると思うので、udevとは違い呼ばれる時には既にxinputは全部揃ってるはず…
  • テンポラリファイルに情報を書いといて、同一デバイスに対するものは60秒以下なら設定せずに抜ける。これで重複する他の呼び出しを全部キャンセル

ということにしてスクリプトを書いたのだけど、ここに書くには余白が少なすぎる。 かなり自分専用でピーキーでチャチなものなので、githubに公開してもなぁ…

SWIG & python でundefined_symbolが出る時

何か超久々ですがとりあえず… とりあえず以下、例として、

  • Flagtileというクラスがあるとします
  • python用のtestlib2モジュールに組み込まれます。
  • SWIG, g++ともコンパイルは問題なく通る
  • C++内では普通に使えている
  • しかし、SWIGPythonモジュールに組み込むと、そのモジュールのimportが通らない。
In [1]: import testlib2
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-e3832f46d55b> in <module>()
----> 1 import testlib2

/home/dothiko/python/test/mypainttest/fill_prog/testlib2.py in <module>()
     26                 fp.close()
     27             return _mod
---> 28     _testlib2 = swig_import_helper()
     29     del swig_import_helper
     30 else:

/home/dothiko/python/test/mypainttest/fill_prog/testlib2.py in swig_import_helper()
     22         if fp is not None:
     23             try:
---> 24                 _mod = imp.load_module('_testlib2', fp, pathname, description)
     25             finally:
     26                 fp.close()

ImportError: ./_testlib2.so: undefined symbol: _ZN9Flagtile25DIRTYE

こんなふうになる時ですね。

これは、私が経験した中では

  1. cppソースとhppで関数の引数が互換性があるが異なった型である (コンパイルエラーにならないので、見過ごしている)
  2. 「hppで宣言されているがcppに実体が存在しない」使用していない関数がある(警告を見過ごしている)
  3. SWIGが対応していない型を使っている

今回の場合は、SWIGが対応していない型、つまりFlagtile内にこのような宣言がある場合だったりしました。

    static const int32_t hogeflag = 0x00000001;

int32_tとかのなんて言うんでしょうか、正確にビットサイズを設定する型。これSWIGさんは嫌いらしいです。

ケツの座りは悪いですがintにすると通ります。 でも、intとかlongとか64ビットで何ビットだっけ?って考えるの嫌なんだよなぁ…

ではでは!

PyGObject(PyGi)で「デフォルトのフォントサイズ」を取得する

PyGObject…でいいのでしょうか。PyGiなのか。未だによくわかりません。 とにかく新しい方のGtkpythonバインディングで、コンボボックスのカスタムレンダラーで使うデフォルトのフォントサイズを取得したくなりました。

これはMyPaintにOpenCVのgrabCut関数を使った新しいペイント方法(グラフカットを利用し穴の開きまくった線画を分離してペイントする)を実装した時、実験中は「真上のレイヤー」を線画レイヤーにしていたのですが、これは実際使いづらい。

そのため、コンボボックスで線画レイヤーを選べるようにしよう!とピコーンと思いついたんですが、カスタムレンダラーでフォントサイズを他のウィジェットと揃える方法が全く分からんのですね。

do_renderコールバックにおけるcairo contextでのtext_extents系関数でサイズを得ると、明らかに小さい文字が描画される…

しかしながら検索方法が悪いのか一向にそのものズバリにヒットしない…

そこでまぁ頑張っていろいろ調べ&試した結果がコレ

ただし、これが正しいのかどうかは不明、という…

from gi.repository import Gtk, Gdk, Pango

st = Gtk.ComboBox.get_default_style()
desc = st.font_desc
size_base = desc.get_size() / Pango.SCALE
if desc.get_size_is_absolute():
    # True == size is in device unit
    pass
else:
    # False == size is in Point.
    # pixel = Point * (DPI / 72.0)
    scr = Gdk.Screen.get_default()
    dpi = scr.get_resolution()
    if dpi < 0:
        dpi = 96.0
    size_base = math.floor(size_base * (dpi / 72.0))
  • スタイル構造体の中にfont_descがあるので、コンボボックスのデフォルトスタイルを得る。
  • font_descのget_size()でサイズを得られるが、これはPango.SCALEでスケーリングされた値であるため、割る。
  • それで得られた値はデバイス単位かポイント単位かに別れる。これはget_size_is_absolute()で区別できる。
  • ポイント単位であった場合、DPI設定 / 72.0 で得られた値を先ほどの値に掛けることでピクセル単位に変換できる。

これで変数size_baseにデフォルトのコンボボックスのフォントサイズ(ピクセル単位)が入る…はず

SWIGとnumpyについて(ImportErrorとセグメンテーションフォールト)

何か「ハリーポッターと魔法のナントカ」みたいなタイトル…

それはともかくSWIGとnumpyでハマってしまったので備忘録的にメモ。

現象としては

  • hppでNO_IMPORT_ARRAYを定義して#include <numpy/arrayobject.h>すると、.pyファイルでSWIGモジュールをインポートした時点でimporterror (undefined symbol: PyArray_API)が起きる。
  • NO_IMPORT_ARRAYを定義しないと、 cppでPyArray_ZEROS()を呼んだところでセグフォが起きる。

苦しんだのですが調べていてようやくわかりました。 基本的に

PY_ARRAY_UNIQUE_SYMBOL

が必要。

  • .iファイルのinit節でimport_array();呼び出し
%init %{
import_array();
%}
  • PY_ARRAY_UNIQUE_SYMBOLをdefineする共通のヘッダファイルを作成
#ifndef COMMON_HPP
#define COMMON_HPP

#define PY_ARRAY_UNIQUE_SYMBOL mytest_array_API

#endif

(↑基本的にmypaintのcommon.hppのパクリ)

  • このcommon.hppを、すべてのヘッダファイルで#include
  • ラッパー用ヘッダでのみNO_IMPORT_ARRAYを定義せず、その他の自作ヘッダではすべて#include <numpy/arrayobject.h>前にNO_IMPORT_ARRAYをdefineする。
  • NO_IMPORT_ARRAYを定義せずに#include “common.hpp"してあると、その都度シンボルが作られるらしくビルドエラーになる

これで、ImportErrorもビルドエラーもセグフォも起きなくなりました。

ところで統合開発環境のPyCharmを試してみたのですがこれは凄いですね。MyPaintも楽々走るしブレークポイントも普通に置けて(当然か…)これヤバイよ!と思いましたが、唯一悲しいのが起動速度の遅さ…まぁ、いいか。まだ、あんまり使ってないのですが。 ああ、あとちょっともっさり気味かもですが、これはまぁ機能の高さとおそらくpythonで書かれてる点からして仕方ないかも。

cgdbを用いてpython拡張モジュール(てか、mypaint)をデバッグする方法

これまた備忘録的に…

  • 当然ながらgccとかg++はデバッグオプション(-g -O0) でビルド
  • mypaintの場合はsconsを使っているため、SConstructに書いてあるようにdebugオプションを使う
  • つまり、scons debug=true
  • デバッグビルドができたらcgdb pythonとする
  • b 関数名でブレークポイントを先行して設置
  • run mypaint
  • この後、関数が呼ばれたところで停止する…のだけど、もしかしてmypaintがX11をgrabしてしまっている場合はX11にアクセスできなくなり操作不能状況になるかもなので注意する。
  • あとは普通にcgdbを動かす

動かしているのがxfce4-terminalだとF10などのcgdbなどとデフォルトではバッティングするので、設定の上級者でF10を(xfce4-terminalにとって)無効化する。これはF10を「xfce4-terminalが」使わなくするので、アプリ側に制御が渡ると言う意味。多分キーバインドが変えられるはずなんだけども…

あとマストな設定がハイライトですかね。

cgdbは「現在の行」にブレークポイントを置くのだけど、ソースウィンドウのどこが現在の行なのかデフォルトでは全然わからない(もしかして、こっちのターミナルの設定が悪いのかもですが)

そこで、~/.cgdb/cgdbrcに

:hi SelectedLineNr cterm=none ctermfg=Cyan ctermbg=Black term=underline

(行頭のセミコロンが必要な模様)

として色を水色にしてしまうとかなりわかりやすい感じがします。とはいえ、ブレークポイントとかちあうとブレークポイントが優先されて見えなくなるのですが、それはそれでまぁ構わないというか…

こうやってやるのか、と勉強になる設定でした…っていうかもしかしてこれなら、netbeansとかのIDEから出来そうな気も?

Python+SWIGでがんばってみた for mypaint (備忘録的)

MyPaintに塗りつぶし拡大ルーチンを設置すべく、Python+SWIGで頑張ってみますた!(`・ω・´)

結果は… 何とか動くようになった!?!?

それで悩んだところを備忘録的に書いておこうと思います。

手こずったところ

まずPyDict_SetItem()。これがキツかった。

こいつのせいで何度セグフォを連発したことか… 原因は、既に存在するアイテムにはPy_INCREFしないという点です。(よくよく考えると当たり前か)

このせいでPy_DECREFを余計にしてしまい開放されてセグフォになってしまいました。

あとなんだろう…何か、手こずった所があったはずなのだけど…PyDictの扱いがインパクト強すぎてすっかり忘れてしまいましたな(^^;

あ、そうそう!PyDict_GetItem()でアイテムが辞書内に存在しない場合、これまた当然ですがCのNULLを返してくるわけですが、何故かPy_Noneを返してくるはずだと思い込んでいてそこでしばらく詰まってましたな〜

デバッグ

デバッグは、gdb pythongdbを起動して break hogefuncで前もって設置、そして run test.pyみたいにするとできます。 cgdbを使うとソースレベル感が増して実に心地よかったですね。まぁこんなんで十分でしょう、別に統合開発環境はいらない…

まとめ

  • ドキュメント少なし
  • でもなんとかなる
  • 慣れればどうということはない

しかしこれでC言語の中からPythonオブジェクトに(比較的)自由自在にアクセスできるようになってきました。 こうなるとPythonの弱点だった実行速度もあんまり関係ない感じになってきますね。

そのうち、ゲームを作りたいところ(´・ω・`) 昔作ってきたところでは作り方も悪かったのでしょう、遅すぎてビックリしました(特に配列周り) 測ってみたらnumpyでもなんでも遅くて、普通のリストが一番早かったという… Cの中に配列を置いて管理させれば良いんじゃないかと妄想中です。てか、javascriptでブラウザゲーのほうがいいか。

PyGiで謎のワーニングに対処する

今試作中のMypaint向けスタンプツールでポップアップメニューをgladeで作るように変更しました (以前はコードから作ってたけど、見通しが悪いので)

それでいろいろ試行錯誤した結果、Gtk.ActionGroupでアクションを作って、このアクションからcreate_menu_itemでメニューアイテムを作ってしまうのが手っ取り早いという結論に達しました。 (pygiからはget_actionが無いとか色々ありまして…)

        actgroup = builder.get_object("actiongroup1")
        for i, ca in enumerate(actgroup.list_actions()):
            nm = ca.create_menu_item()
            nm.show()
            self.menu2.insert(nm, i)

こんなような感じのコードで複数のpopup menu間の共有メニューをinsertしていくような感じですね。

しかし、そうしているとこのような謎のワーニングが大量に出るはめに。

Gtk-CRITICAL **: gtk_accel_label_set_accel_closure: assertion 'gtk_accel_group_from_accel_closure (accel_closure) != NULL' failed

なんのこっちゃ?といろいろ調べてもよくわからない。

最終的にgladeでActionGroupに対して空のアクセラレーターグループを作って付けて置けば起きないということがわかりました。

f:id:dothiko:20161218214821j:plain

こんなふうですね。実は、アクセラレーターグループって一体何なのかよく分かっていません(^^;

無駄な時間を過ごしてしまった…(汗