LOKDocView

本日の Fedora アップデートでこんなのが出た。

LOKDocView って何だ?

Development/Integrating LOKDocView and GNOME Documents – The Document Foundation Wiki

libreoffice 文書を GNOME Document で表示する API なのか。
ということは gir でバインドされているのかな。

普通にあったわ、コレって今まであったっけ?
まあそれはいいや、さてサンプルコードを探してみよう。

GitHub – pranavk/lokdocviewer: An application for testing LOKDocView

js/py 両方用意してくれているとは親切ですね。
しかし new メソッドの引数が PyGObject は 2 つで Gjs は 3 つ。
Gjs らしく new キーワードに書き換えるとコアダンプ。

#!/usr/bin/gjs

const Gtk = imports.gi.Gtk;
const LOKDocView = imports.gi.LOKDocView;
const Lang = imports.lang;

const LOKWindow = new Lang.Class({
    Name: 'LOKWindow',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        // Segmentation fault
        //this.view = new LOKDocView.View();
        this.view = LOKDocView.View.new(null, null, null); // OK
        this.view.open_document(
            "/home/sasakima-nao/syokumu.odt",
            "{}",
            null,
            Lang.bind(this, function() {
                this.view.set_edit(true);
            }),
            null);
        let sw = new Gtk.ScrolledWindow();
        sw.add(this.view);
        this.add(sw);
        //
        this.show_all();
    }
});

let app = new Gtk.Application();
app.connect("activate", function() {
    new LOKWindow(app);
});
app.run(null);

前々回みたいな問題があるし…
GNOME はぶっちゃけ Gjs に全面移行したいのだろうけどまだ問題が多いなぁ。
てか Python はサードパーティなのに対応っぷりがスゲェ。
当面は Python と併用が続くのだろう。

コレを使って何か作るかな、ここんとこネタ切れなのは秘密だよ。

Koiking

久々に面白いスマホゲーム発見。
『はねろ!コイキング』公式サイト

どうせまた数日で飽きると思っていたけど続いている。
ただポケ GO みたく現在時刻も表示してほしいな、休憩時間にちょっと育成って時に困る。

検索したらガチな人が結構いてビックリ。
ポケ GO のおかげでキャラがある程度解る人が多いのが大きいのかも。
筆者はガンダム世代だしポケ GO が出なかったらポケモンなんて知らないまま。
ゲームはやはりキャラだよ、豪華声優はゲンナリするだけ。

Linux プログラミングに飽きてきたのでスマホアプリとか考えたけど…
ゲームって内容よりキャラが重要なんだよな、うーん。

ClutterImage PyGObject/Gjs

おまたせ、Comipoli Gjs 版が遅くて出せない原因が判明しました。

#!/usr/bin/env python3

import gi
gi.require_version('Clutter', '1.0')
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Clutter, Cogl, GdkPixbuf

Clutter.init()

PICTURE = "burgman400.jpg"

pixbuf = GdkPixbuf.Pixbuf.new_from_file(PICTURE)
image = Clutter.Image()
image.set_data(
    pixbuf.get_pixels(),
    Cogl.PixelFormat.RGB_888,
    pixbuf.get_width(),
    pixbuf.get_height(),
    pixbuf.get_rowstride()
)

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const Cogl = imports.gi.Cogl;
const GdkPixbuf = imports.gi.GdkPixbuf;

Clutter.init(null);

const PICTURE = "burgman400.jpg";

let pixbuf = GdkPixbuf.Pixbuf.new_from_file(PICTURE);
let image = new Clutter.Image();
image.set_data(
    pixbuf.get_pixels(),
    Cogl.PixelFormat.RGB_888,
    pixbuf.get_width(),
    pixbuf.get_height(),
    pixbuf.get_rowstride()
);

何ですかこの圧倒的なスピード差は!!!
というより Gjs のこの異様な遅さは何なんだ?
GdkPixbuf を使うだけなら特に差が無いのに。

憶測だけど PyGObject は get_pixels でバイナリ出力を直接使っていると思う。
Gjs は多分バイナリ出力を Uint8Array オブジェクトに変換している。
言語仕様の制限だろうからセット関数の追加が無いかぎりこのままだろうね。

ClutterImage に画像セットはコレしか手段が無い、これは困った。
Gjs でいくなら ClutterCnavas で cairo という手しか無いっぽい。
でもそれなら GtkDrawinArea でいいじゃん、ということになり…

結論、Clutter で画像を使うのに Gjs は絶望的に向いていません。

G_PRIORITY_DEFAULT

連休なのに更新ができないままです。
しかたがない、Comipoli Gjs 版で行き詰まっているままですので。
何故こんなに遅いのかまだ解明できていない。

とりあえず他の部分だけでも仕上げておくか。
とやっていたらヒント?かもしれないことを発見。

g_idle_add でバックグラウンド動作ができない場合があった。

その部分だけ抜き出ししてサンプルコード。
PATH 定数は手持ちな巨大サイズの CBZ ファイルに書き換えてね。

const Lang = imports.lang;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const GdkPixbuf = imports.gi.GdkPixbuf;

const PATH = "/home/sasakima-nao/doc/comic/bigfile.cbz";
const SIZE = 120;

const NewArchive = new Lang.Class({
    Name: 'NewArchive',

    _init: function(path) {
        this.namelist = [];
        this.path = path;
        let l = path.split('.');
        let ext = l[l.length-1].toLowerCase();
        //
        if (ext == "cbz") {
            let sp = Gio.Subprocess.new(["unzip", "-Z", path], Gio.SubprocessFlags.STDOUT_PIPE);
            let istream = sp.get_stdout_pipe();
            let dstream = new Gio.DataInputStream({
                base_stream: istream
            });
            for (;;) {
                let [line, len] = dstream.read_line_utf8(null);
                if (line == null) break;
                if (line.startsWith('-')) {
                    let name = line.slice(53);
                    if (GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0)) {
                        this.namelist.push(name);
                    }
                }
            }
            this.namelist.sort();
        }
    },
    get_item: function(num) {
        let sp = Gio.Subprocess.new(["unzip", "-p", this.path, this.namelist[num]], Gio.SubprocessFlags.STDOUT_PIPE);
        let stream = sp.get_stdout_pipe();
        try {
            var pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
        }
        catch(e) {
            print(this.namelist[num]);
            print(e);
        }
        stream.close(null);
        return pixbuf;
    },
    get_length: function() {
        return this.namelist.length;
    }
});

const ThumbnailWindow = new Lang.Class({
    Name: 'ThumbnailDialog',
    Extends: Gtk.Window,

    _init: function() {
        this.parent();
        this.archive = new NewArchive(PATH);
        this.fbox = new Gtk.FlowBox({
            valign: Gtk.Align.START,
            max_children_per_line: 10,
            homogeneous: true,
            selection_mode: Gtk.SelectionMode.BROWSE
        });
        let scroll = new Gtk.ScrolledWindow();
        scroll.add(this.fbox);
        this.add(scroll);
        this.resize(1024, 480);
        this.show_all();
        // idle
        this.gen_func = this.read_zipfile();
        //
        // not G_PRIORITY_DEFAULT or G_PRIORITY_HIGH_IDLE
        //
        //GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
        GLib.idle_add(GLib.PRIORITY_LOW, Lang.bind(this, function() {
            try {
                this.gen_func.next();
                return true;
            } catch(e) {
                return false;
            }
        }));
    },
    read_zipfile: function() {
        let l = this.archive.get_length();
        for (let i=0; i<l; i++) {
            let pixbuf = this.archive.get_item(i);
            this.append_data(pixbuf, i);
            yield 1;
        }
    },
    append_data: function(pixbuf, num) {
        if (pixbuf.get_width() > pixbuf.get_height()) {
            var pix_w = SIZE;
            var pix_h = Math.round(SIZE * pixbuf.get_height() / pixbuf.get_width());
        } else {
            var pix_w = Math.round(SIZE * pixbuf.get_width() / pixbuf.get_height());
            var pix_h = SIZE;
        }
        let minp = pixbuf.scale_simple(pix_w, pix_h, GdkPixbuf.InterpType.TILES);
        let image = new Gtk.Image({
            pixbuf: minp,
            visible: true
        });
        this.fbox.add(image);
    },
    vfunc_hide: function() {
        Gtk.main_quit();
    }
});

Gtk.init(null);
new ThumbnailWindow();
Gtk.main();

抜き出したのに長い…

最初は何も考えずに G_PRIORITY_DEFAULT 指定。
すると for 文で回した時のように全部展開されるまで表示されない。
これじゃ g_idle_add の意味が無い。

バックグラウンド動作させるための関数に何故こんな機能を付けたのだ?
てか、原因がコレだと解るのに一日使ったってばさ。
ということで、皆さん DEFAULT という定数名は疑ったほうがいいかも。

もしかして遅い原因もコレ関係かと探しているけど見当たらない。
this.read_zipfile = function(){}
みたく指定していたのを上記で書き換えたら少し早くなった、気がする…

明日から筆者は仕事だ!
そんなこんなで連休では完成できませんでした。

苗ちゃんフルマカロンまであと一つ、炭酸はギリギリ足りそうだ。
元浜公園に行ったらポケストップが無くなっていてしょんぼり。
いや、息抜きですよ。

Gjs VS PyGObject (Linux time command)

Linuxコマンド集 – 【 time 】 指定したコマンドの実行時間を表示する:ITpro

なんだよ、こんな便利なコマンドがあったのかい!
これで Python と Gjs の速度比較が簡単になる。

function printDateFormat(ms) {
    let date = new Date(ms);
    let s = date.getMinutes() + ":" + date.getSeconds() + ":" + date.getMilliseconds();
    print(s);
}

ってのを Gjs 用に考えたけどいらなかった。

てなわけで、早速 Gio.Subprocess で差が出るか確認してみよう。
前回のコードから不要な部分をバッサリ省いて。

#!/usr/bin/env python3

import gi
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Gio, GLib, GdkPixbuf

namelist = []
unzip_list = []

sp = Gio.Subprocess.new(["unzip", "-Z", "test.cbz"], Gio.SubprocessFlags.STDOUT_PIPE);
istream = sp.get_stdout_pipe()
dstream = Gio.DataInputStream(base_stream=istream)
while True:
    line, l = dstream.read_line_utf8()
    if line == None: break
    if line.startswith('-'):
        name = line[53:]
        if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
            namelist.append(name)

for name in namelist:
    sp = Gio.Subprocess.new(["unzip", "-p", "test.cbz", name], Gio.SubprocessFlags.STDOUT_PIPE)
    stream = sp.get_stdout_pipe()
    p = GdkPixbuf.Pixbuf.new_from_stream(stream, None);
    unzip_list.append(p)
    stream.close()

Gjs で書き換えて。

#!/usr/bin/gjs

const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const GdkPixbuf = imports.gi.GdkPixbuf;

let namelist = [];
let unzip_list = [];

let sp = Gio.Subprocess.new(["unzip", "-Z", "test.cbz"], Gio.SubprocessFlags.STDOUT_PIPE);
let istream = sp.get_stdout_pipe();
let dstream = new Gio.DataInputStream({base_stream:istream});
for (;;) {
    let [line, l] = dstream.read_line_utf8(null);
    if (line == null) break;
    if (line.startsWith('-')) {
        let name = line.slice(53);
        if (GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0))
            namelist.push(name);
    }
}

for (let i=0; i<namelist.length; i++) {
    let sp = Gio.Subprocess.new(["unzip", "-p", "test.cbz", namelist[i]], Gio.SubprocessFlags.STDOUT_PIPE);
    let stream = sp.get_stdout_pipe();
    let p = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
    unzip_list.push(p);
    stream.close(null);
}

コレを time コマンドで比較。

って一秒すらも差が出ない誤差の範囲ジャン!
Gjs 版 Comipoli が異様に遅い原因は言語のせいでは無いことは判明した。