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

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

Xubuntu(Ubuntu+xfce)16.04でのトリプルモニタ環境の設定

Xubuntu 16.04 (≒Ubuntu + xfce) 、今までデュアルモニタ環境で快適に使っていましたが、実はコレ偶然の産物だった…という話。

前述のようにwacom cintiq13HDを導入したため、トリプルモニタ環境にしたのですね。ちなみにGPUnvidiaGeForce 750GTXです。 それで設定したままサスペンド運用してきて、今日IHコンロやら何やらの使い過ぎでブレーカーが落ちて停電しました(^^;

やはりIHはクソ。地球上から駆逐されるべき存在。今すぐガスに戻せ

なんて話はどうでもよくて、んでリブートしたらファイルシステムがcorruptしていて立ち上がらない…のは仕方なかったのでfsckしてなんとか立ち上がるようにはなったのでまあよいとして、

リブートしてようやく思い知ったことはといえばnvidia-settingsを使っても

全然トリプルモニタ環境を覚えてくださらない…

ということです。

しかし… nvidia-settingsのモニタ設定ってどっかに記録されるわけじゃないんですね。*1今までのデュアルモニタ時代は、偶然に上手いこと設定が自動で行われた…というだけのことのようです。ただのおまけユーティリティなのでしょうか。(立派に見えるのでついつい使ってしまいますな〜)

それにつけても、まずはHDMI-0、つまり通常電源を入れていないcintiqがプライマリにされてしまうようで、ログインダイアログが見えない。これはきもい。

これについては、ググった結果「~/.config/monitor.xmlなる謎のファイルが悪さをしている」という話があったので、一応リネームで様子を見ることに。 確かにこれでDisplayportがプライマリになり、無事メインのEIZO EV2436Wにログインダイアログが出るようになりました。

しかし…この状況下でも、なんか、cintiqとサブモニタが重なって右に表示されてしまうのですね。つまり「サブのみクローン」状態。

ただし、これはこれで実際便利な気もします。 デュアルモニタ状態で実質トリプルのほうが、色々とっちらからなくて宜しい、という考え方ですね。むしろプライマリに重ねてしまうという手もあるのかも。 *2

ただ、トリプルモニタ環境を思ったとおりに構築可能な状況で「サブのみクローンのが便利じゃん」って移行するのなら良いのですが、せっかくLinuxを使っているのに妥協はよくない、ということでさらに調べる事に。

んで色々諦めて最後の望みとして、絶対確実なはずの方法、つまりxfceの「自動起動アプリケーション」でxrandrを呼び出すスクリプトを書いた…しかし!

なんと、これも機能してくださらない!

/tmpに標準出力を書きだしたところ、呼び出しは行われていて直後のxrandrでは正しく設定が行われている。しかしその後何者かによって設定が書き換わって居る模様。

調べた結果どうも、xfceの設定が直後に行われているっぽいのです。

結論としては…

xfceでの作法としては「設定」→「ディスプレイ」で出るミニ設定アプリでモニタ設定するのが、恒久的にモニタ設定を記憶させる正しい方法のようです。

f:id:dothiko:20180112142827j:plain

このアプリですね。*3

ではそういうことで(疲れた…)

*1:固定化の為にxorgのconfを吐いてくれるらしいのですが、何かパッケージが足りないのか吐いてくれなかったし、それ以前に今の時代にxorg.conf.dにモニタ設定というのも…

*2:てゆーか色々買って不良品も掴んでみたHDMI分配器、あらゆる面で完全に要らなかったな。ハハハ…

*3:正直いままで「このディスプレイってのはつかえねー設定アプリだな…」と思っていましたが私が間違っていたようです

Gesturefyとevdevで作る快適3ボタンマウス環境

FirefoxプラグインFiregestureが使えなくなったため、Gesturefyを導入しました。 これがまた、なかなか良い。

なお、ジェスチャー&ロッカージェスチャー*1は、現時点ではlinux/macにおいては、右ボタンが押下時にコンテキストメニューを発動するため、コンテキストメニュー発動を回避できず、右開始のジェスチャーはまともに使えません。 しかしながら、これはver 58以降のfirefoxでは改変されるそうなので非常に楽しみです。 *2

それまでの間は、ジェスチャーは左ボタンで起動することにしました。うっかり範囲選択を始めるとジェスチャーに入ってしまうのですが、Gesturefy設定によるジェスチャーの無効化「Ctrl押下でキャンセル*3」と、「一秒じっとしてればキャンセル」を併用すれば実用上問題はないので慣れの問題ではあります。 まぁ、早く来てくれ右ボタンー!! としか言いようがないのですが。

ともかく、それで戻るボタンが不要になったため、3ボタンマウスm100rではevdevのホイールエミュレーションを中ボタンに割り当ててみました。

xinput --set-prop ${id} 'Evdev Wheel Emulation' 1
xinput --set-prop ${id} 'Evdev Wheel Emulation Button' 2
xinput --set-prop ${id} 'Evdev Wheel Emulation Axes' 6 7 4 5
xinput --set-prop ${id} 'Evdev Wheel Emulation Inertia' 2

これやると2ピクセルぐらいで1スクロールになるので超かっとび・超快適です。

マウスホイールなんか要らなかったんや!と言うレベルの…いや、実際にはあったほうがいいの…かな…細かい動きを指定したいときは、やっぱりまだ役に立ちますかね。

できれば遅く動かしたときはイナーシャを大きくする仕組みがあればなぁ、と…まぁ、いいか。

となってくるとコレ、ミドルボタンをサイドに取り付けた私の改造マウスではなく、むしろ無改造の3ボタンのほうが使いやすいのでは?という疑問が沸いてきましたが まぁ「それはそれ」ということで。てか、ホイールクリックの反応の悪い事を考えれば、サイドもわるくはないです。

試しに、以前買ったナカバヤシのMUS-UKF90NBKで、サイド「進む」にホイールエミュレーション、 「戻る」にミドルボタンをマップしてGesturefy発動という設定にしたところ、これはこれでかなり快適でした。

いままで、自分の手に対してホイールの位置が合わないことでサプライ系各社の安マウスには大変苦しめられてきましたが。

それでもLinuxのevdev環境下においては、5ボタンあればホイールは本当に補助的になるため、今後はサプライ各社の安マウスを導入しても全く問題ないという事がわかってきました。

つぅかね…大昔3ボタンだけのホイール無しのシンプルマウスがあったじゃないですか。*4

アレでいいと思うんですよ。

んでevdevのようなドラッグによるホイールエミュレーションをハードウェアレベルで入れてしまえばいい。 だからセンターは都合2ボタンになりますが。フリースピンホイールとどっちが速いかはわかりませんが…操作性考えれば、かなりいい勝負になると思います。

更に言うとサイドに戻る進むも付けた、6ボタンホイール無しマウス。そりゃ10ボタンぐらいあれば言うことないですが、ここは敢えて限界までそぎ落として6ボタン。

これ最強だと思うんですがどうか。

*1:マウスボタンを右→左で同時押し/左→右で同時押しでそれぞれ別の機能を発動するジェスチャー

*2:なお、現段階でも、ロッカージェスチャーの右→左で「戻る」を行いたいときは、コンテキストメニューが出たままで左ボタンをダブルクリックすると「戻る」が発動されます。

*3:Shiftも設定できるのですが、何故か範囲選択できない場合が多い

*4:今もあるようですが中身に比して超ボッタクリな感じが…とくにHPのやつ

Blenderが起動しないように見えるとき(@トリプルモニタ環境)

遅ればせながら、あけおめことよろでございます。

さて私のPC環境は大きく変化し、Wacom cintiq13HDを導入したことでトリプルモニタ環境となりました。 ちょっと変則的ですが、正面と右にモニタを配置。これは今までと同じ。 次に、正面の下…つまり机の上にcintiq13HDを置くという形になります。

これで自然にマウスカーソルを主画面の下に動かすと、cintiqの中にカーソルが入ってくるというようになりました。 そして、貧乏性が災いし、cintiqは通常HDMIに接続されているものの、電源は落としてあります。この状況下で、XWindowシステムにモニタとして認識はされ続けており、トリプルモニタ環境は継続しています。

見えるかどうかだけの違いであり、cintiqの電源を投入すれば、USBのペンタブデバイスプラグインされるだけで、モニタとしてはシステム上、ほぼ何も変化なしに、単に画面が見えるようになるという、理想的な状況です。

さてここで問題が発生しました。

Blenderが起動しない(ように見える)

この状況下で、何故かBlenderが起動しない。画面が全く見えない。 psで見るとプロセスは発生している。

Alt+Tabを押すとやっぱりあるなぁ。

そこでwmctrl -lGすると。やはり、ウィンドウは出ています。しかし、位置が…cintiqのなかにいる!

デュアルモニタではせいぜい2つの画面にまたがるぐらいなのですが、どうも変則的なシステムだと「最初に見つかるモニタ」に行く傾向がある模様であり、我がシステムではそれはHDMIだという…それが今回の悲劇を生んだようです。

Blenderウィンドウが動いてくれない。

調べると-pオプションを与えることで起動位置を指定できるようです。

しかし、これがまた何故かサイズだけ適用され位置が機能しない。 orz

追記:と思ったらワタクシ間違えておりました。詳細は下の「Blenderの-pオプションの正解」に書いておきました…が、一応wmctrlの効かない状況で、xdotoolでウィンドウを移動するためのサンプルとして以下に残しておきます(^^;

仕方ない、wmctrlで移動させるしかないか…

…ところが、wmctrlの-eオプションではBlenderのウィンドウを動かすことが出来ないようです。 (コレは実はKritaも同じで、xdotoolで移動できます)

仕方ないので、xdotoolを使用します。 というわけでスクリプト化してみました…

#!/bin/sh
width=1920
height=1200
x=0
y=0
title="Blender"

blender -p $x $y $width $height &
sleep 1
id=`wmctrl -lG | grep ${title}\$ | cut -f 1 -d " "`
xdotool windowmove $id $x $y

後々汎用化させることも視野に入れ、大体のところは変数化しました。

Blenderの-pオプションの正解

何気なく再度manpageを見ると…

       -p or --window-geometry <sx> <sy> <w> <h>
              Open with lower left corner at <sx>, <sy> and width and height as <w>, <h>

lower left corner って書いてあるわw

だから正解はwmctrlやらxdotoolやらを使うのではなく、pオプションだけ、

blender -p 0 1200 1920 1200

で良いということになりますな…相変わらずアホなワタクシでございます。

おまけ - Blenderでマウス中ボタンを代用する

ワタクシ、また安マウスを購入する病気が発症しそうで苦しんでいるのですが、Blenderでは中ボタン(ミドルボタン)でビューを操作できるのが重宝されているようです。

それでもって私はマウスのミドルボタンを改造して側面に付けて別のことに使っているのでした。 以前は戻るボタンに割り振っていましたが、FirefoxのGesturefyを導入して以来、「戻る/進む」はジェスチャー(ロッカージェスチャーも)で代用できるようになったため、 現在はevdevによる、ドラッグでの快速なスクロールエミュレーションに使用しています。 つまり、ボタンが足りません。 このため、またも5ボタン以上のマウスが欲しくなり禁断症状が発症しているのでした。

そんなわけでミドルボタンを代替する方法はないものか、と調べたらあっさり見つかりました。

User preferences -> Input -> Emulate 3 button mouse

これをチェック入れるだけでAltキー押下+左ボタンでビューをグリグリ回せるのですね。とりあえずはコレで行きます。

SWIGオブジェクトから生のポインタを取り出すには

最近SWIGづいていますが… PythonSWIG(SWIGPythonか…?)ラッパーオブジェクトからポインタを取り出すにはどうすればいいのか?

それも、自作のクラスオブジェクトでございます。 なんでこんなことをする必要があるのかというと、SWIG化クラス「foo」があるとして…

import swig_module

bar = []
for i in range(10):
    bar.append(swig_module.foo()) # fooクラス生成

swig_module.call_swig_list(bar) # リストを突っ込む

こんな感じの時ですね。fooインスタンスは、pythonで何かするためにやむをえず作ったリスト配列の中に入っており。 それでcall_swig_listの中で取り出すと、それはPyObject* なわけです。

ここから、C++クラス「foo」のポインタを取り出して、C++コードの中で呼びたい。

「各fooインスタンスを引数にしてforループの中で呼ぶようにすればいい」という状況のほうが多そうですが、そうも行かないことがある。 まぁ例えば大量のfooインスタンスがdictの中に入っていて、そのキーも重要な情報だったりするので一遍に処理したいとか。

正直、ただのこだわりです。

結構苦しめられましたがわかりました。

  • SwigPyObjectという型(構造体)があるのだけど、これが.ccという拡張子のSWIGによる自動生成ファイルに入っている。
    • .ccファイルの中は、非常にマクロ宣言が多くキモい。
  • だから手っ取り早く使うにはもう自分で定形のを宣言してしまうのが良い。
typedef struct {
  PyObject_HEAD
  void *ptr;
  void *ty;// Dummy definition. Actually, this is swig_type_info*. 
  int own;
  PyObject *next;
  PyObject *dict;
} SwigPyObject_base;
  • さてこのptrというのがそのオブジェクトポインタか…といえばさにあらず。
  • 実は、SWIGがデフォルトでサポートしていない型の場合、露出するオブジェクトの「this」属性に、生ポインタを保持したSwigPyObject*が格納されるという、2段階構造になっている。

やっと動いたテスト用のSWIG関数だけ抽出して書いておきます。

PyObject* call_swig_list(PyObject* pylist) {
    int length = PyObject_Length(pylist);
#if PY_VERSION_HEX >= 0x03000000
    PyObject* swig_this = PyUnicode_FromString("this"); 
#else
    PyObject* swig_this = PyString_FromString("this");
#endif
    for(int i=0;i < length;i++) {
        SwigPyObject_base* o = (SwigPyObject_base*)PyList_GetItem(pylist, i);
        // Make shadow instance as real instance.
        o = (SwigPyObject_base*)PyObject_GetAttr((PyObject*)o, swig_this); // new reference.
        foo *f = (foo*)(o->ptr);
        printf("objectptr %lx \n" , f);
        f->method1(i); // これでクラス「foo」のインスタンスのメソッドを呼べる!
        Py_DECREF(o);
    }
    Py_DECREF(swig_this);
    Py_RETURN_NONE;
}

多少、強引ですが…動けばよかろうなのだァァァァッ!!

…って、動けばよかろうなら最初に書いた「python側でforループ展開」でいいだろ、って気が自分でもしますが(^_^;)

ともかくこれをつかって…ヌフフ

Gdk(pygi)でマウスカーソルを任意位置に動かすには

感動のあまりカキコ!

改造版mypaintのsizechangemode.pyから一部抜粋〜

    def drag_start_cb(self, tdw, event):
        self._ensure_overlay_for_tdw(tdw)
        self._queue_draw_brush()
        # Storing original cursor position and some needed objects.
        dev = event.get_device()
        pos = dev.get_position()
        self._dev = dev
        self._scr = event.get_screen()
        self._ox = pos.x
        self._oy = pos.y
        super(SizechangeMode, self).drag_start_cb(tdw, event)

    def drag_update_cb(self, tdw, event, dx, dy):
        self._ensure_overlay_for_tdw(tdw)
            
        self._queue_draw_brush()
        adj = self.app.brush_adjustment['radius_logarithmic']
        cur_value = adj.get_value() + (dx / 120.0)
        adj.set_value(cur_value)
        self._queue_draw_brush()
        super(SizechangeMode, self).drag_update_cb(tdw, event, dx, dy)

    def drag_stop_cb(self, tdw):
        self._ensure_overlay_for_tdw(tdw)
        self._queue_draw_brush()
        self.start_drag = False
         
        self.base_x = None
        self.base_y = None

        # Reset cursor positon at the start position of dragging.
        Gdk.Device.warp(self._dev, self._scr, self._ox, self._oy)
        super(SizechangeMode, self).drag_stop_cb(tdw)

概要としては

  • event構造体のget_device(), get_screen()が必要。
  • mypaintのdrag_stop_cbにはeventがないので保存しておく
  • 位置はスクリーン座標なのだけど、deviceオブジェクトのget_position()でなんか謎のオブジェクトが得られてそいつの属性x,yがスクリーン座標なので、それを保存しておく。
  • ドラッグ終了時にGdk.Device.warpですっ飛ばす

これで、いままでシフトキー押下で発動するように設定していたオンキャンバスなブラシサイズ変更モード。

これが、サイズ変更でカーソル動いちゃって悲しい感じだったんですが (kritaを真似したのだけど、あっちはちゃんとカーソルが固定される)

ようやく思い通りになりました!

追記:

…と、マウスで実験してみた時は良かったのですが… ペンタブでやってみると、やはりペンタブを動かしてサイズを変更するとカーソルが移動してしまう。 ナンデ? と思ってよくよく考えてみると…

> ペンタブは絶対位置デバイスだから離した瞬間に別の絶対位置に移動している <

という、実にマヌケな結果に… じゃあ、Kritaはどうやってそれを防いでいるのか?

と思ってやってみたら(当たり前ですが)Kritaでも同じ挙動でした(>_<) 記憶が間違っていただけという悲しい結果に。

SWIG関数にctypesからアクセス

ついでにこれも思いついたシリーズ。

すなわち例えば、SWIG側の内部にstaticで構造体ポインタのリストを持っておき。 idx = register_hogehoge(void *ptr)でそのリストに登録。 その後はidxを使って(OpenGLのテクスチャの名前みたいな感じで)、例えば draw_bg(idx, 0, 0) みたいなことをすると。まぁ、これだとオブジェクトが削除されたのを知るのは難しそうなので、そのへん十分に気を使って、と。 どうにかすればctypesのオブジェクトにincref的なことができるのかもですが、まだよく分かっておりません。 あ、ていうかよく考えると表面はPyObjectだろうから普通に行けちゃったりするのかな…

ともかく、そういういうことを思いついたのでやってみました。 特に普通の共有ライブラリと違う点もなく、簡単にできました…ポイントは * ctypesの名前関係が難しくなるのでextern "C" でその場を凌ぐ * void*だと、構造体を引数に入れただけだと自動変換してくれないっぽいので、pointer(obj)を使う * つかcast(pointer(obj), c_void_p)が正しい? まぁLinuxだから簡単だったのかも知れないですけどね。

その他、ctypesの関数にSWIG内から…というのも妄想していましたが、よくよく考えると上記の「SWIGをctypesで呼ぶ」作戦のほうが柔軟性も自由度も高そうなので、とりあえずこれでいいかなと。

最初に思いついたのはctypesでポインタをlongにする関数を作って呼び出し、SWIGでそのlong値を受けてそれを強引に構造体ポインタにキャストする…というものでした。実際に動いたのですが、実になんかその…ダサいというか無理矢理感が強すぎたので上記の方法を考えました。

もしかするとswigに直接ctypesの構造体のアドレスをvoid*ポインタとして送り込めるのかもですが、そのへん全然わからないのでした。

PythonでSWIGのC++関数中でGdkPixbufにアクセスしてみた

Pythonで作ったGdkPixbufを、SWIGC++関数に送り込んでC++の中で最速描画ということを考えつきました。

さらに、そのpixbufはcairoから作ると、なお痛快でしょう。 と前々から思っていたのですが、遂に重い腰を上げて作ってみました。

そんなわけでそのサンプルをここに置いておきますね…(´・ω・`) 引数チェックは気分的にしたい部分以外はしていないですがテストなので…

testlib.i

SWIGのインターフェースファイルです。

%module testlib
// Inserting block
%{
#include "testlib.hpp"
%}
// Combining block
%include "testlibcore.hpp"
%init %{
%}

testlib.hpp

結合用のヘッダです。testlib.ccに埋め込まれるはず。

#include <Python.h>

// Write my own libraries
#include "testlibcore.hpp"

testlibcore.hpp

宣言だけなので書く必要無いですが一応

#ifndef __TESTLIBCORE_H__
#define __TESTLIBCORE_H__

#include <Python.h>

PyObject* testfunc(PyObject* pixbuf);

#endif 

testlibcore.cpp

#include "testlibcore.hpp"
#include <pygobject.h>
#include <stdio.h>
#include <gtk/gtk.h> //これでOK

PyObject* testfunc(PyObject* pixbuf) {

    GObject *obj = pygobject_get(pixbuf);
    if(obj != NULL) {
        printf("--- success to get gobject ---\n");
        GdkPixbuf* gpix = GDK_PIXBUF(obj);
        if(gpix != NULL) {
            printf(
                "the pixbuf is w:%d, h:%d, channnels:%d\n" ,
                gdk_pixbuf_get_width(gpix),
                gdk_pixbuf_get_height(gpix),
                gdk_pixbuf_get_n_channels(gpix)
            );
            // writing blue box
            int rowstride = gdk_pixbuf_get_rowstride (gpix);
            guchar *pixels = gdk_pixbuf_get_pixels (gpix);
            int n_channels = gdk_pixbuf_get_n_channels (gpix);

            int sx = 30;
            int sy = 30;
            int w = 48;
            int h = 20;

            for (int y=sy; y < sy+h; y++) {
                for (int x=sx; x < sx+w; x++) {
                    guchar *p = pixels + y * rowstride + x * n_channels;
                    p[0] = 0; //r
                    p[1] = 0;    //g
                    p[2] = 255;     //b
                    p[3] = 255;        //a
                }
            }
        }
        else {
            printf("--- too bad, failed gdkpixbuf ---\n");
        }
    }

    Py_RETURN_NONE;
}

ビルド用のSConstruct

いちおう、scons用のファイルも置いときます。いやべつにmakeでもいいんですが難しいので…

Target='_testlib.so' # 先頭に_が必要(超重要!)
srcs = "testlibcore.cpp testlib.i" # testlib.c is automatically generated.

e = Environment(SWIGFLAGS=['-c++', '-python'])
e.Append(CCFLAGS=['-Wall', '-g', '-O0', '-fPIC', '-fdiagnostics-color'])

e.ParseConfig('pkg-config pygobject-2.0 glib-2.0 gtk+-3.0 --cflags --libs')
e.ParseConfig('python-config --includes --ldflags')

e['SHLIBPREFIX'] = '' # To avoid add 'lib_' prefix to target library file.
e.SharedLibrary(Target, Split(srcs))

テスト実行用のcairo_pixbuf.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import cairo
from gi.repository import Gdk, GdkPixbuf
import testlib

def main():
    w = 320
    h = 240
    surf = cairo.ImageSurface(cairo.FORMAT_A8, w, h)
    cr = cairo.Context(surf)
    cr.set_source_rgba(0, 0, 0, 1.0)
    cr.rectangle(8, 8, 100, 200)
    cr.fill()
    pixbuf = Gdk.pixbuf_get_from_surface(surf, 0, 0, w, h)
    print("---from python")
    print("sample bits:%d" % pixbuf.get_bits_per_sample())
    print("channels:%d" % pixbuf.get_n_channels())
    print("---calling c")
    testlib.testfunc(pixbuf)

    pixbuf.savev("/tmp/test_blued.png", "png", [], []) 

if __name__ == '__main__':
    main()

これを実行すると/tmp/test_blued.pngとして、 python側でcairoで四角を書かれたpixbufに青い四角が書かれたものが保存されるという。 まったくどうでもよい単なるテスト機能が発動するのでした。 なお、どうも上記のようにFORMAT_A8でグレースケールのcairoサーフェスに描画しても、その時はグレースケールで処理されますが、その後Gdk.pixbuf_get_from_surfaceでpixbuf変換すると32bitのRGBAになってしまうようです。まぁ構いませんが。

しかし、これは実に応用できそう… PygameサーフェスにCからアクセスしたりとか、前々から妄想していたのです。 これをやれば超高速BGタイル描画とか出来そうです(pythonは配列アクセスが遅いので…でも、どうせプロトタイプにしか使わないし、あんま意味ないかw) んでいろいろ調べましたが、コレも結構簡単に出来そうです。_pygame.hによるとPygameサーフェスはPySurfaceObjectで、PySurface_AsSurface(x)マクロがあるようです。

さらにPygameの実質後継と言われているらしいPySDL2も出来たら良いね、と思って調べましたが… PySDL2は、ctypes使ってるんですね。 PyOpenGL3もそうですが… つまりPySDL2とかでは同様にctypesで自作C関数にアクセスすれば、そのままぶっこめるという、実にしまらない落ちに(^^;