JavaScript」タグアーカイブ

Gjs on Visual Studio Code

Fedora 28 でプログラミング。

っても GTK+ の大幅な変更は二年毎になったので 29 までメンテのみ。
半年毎にゴロッと変わる GTK3 初期の頃は今考えると結構楽しかった。

OpenJDK は 1.8 のまま、1.9 から jjs が es6 完全対応なんだが。
Gjs はプロファイラがどうたららしいけど、正直どうでもいい。
Python3 も 3.6 のまま、つーことで何も変化無し。
変わらなすぎて面白くないのは筆者だけなのだろうか。

特に書くこともないので終わり、では悲しいので。
Visual Studio Code で gjs アプリ(comipoli)の F5 実行でも。
長いので以下 vscode と略。

書くまでもなく”ウザい!邪魔!迷惑!”のコード保管は全部無効に。
Visual Studio Code 2018 | PaePoi
現在は更に以下を追加。

// 拡張機能の推奨なんて超迷惑
"extensions.ignoreRecommendations": true,
"extensions.showRecommendationsOnlyOnDemand": true

vscode を起動しウエルカムページから開発したいアプリのディレクトリを開く。
キーボードショートカットの設定を開く。
F5 で検索して Node.js の debug 実行のキーを Delete キーで殺す。
Node.js もやりたい人はデフォルトのキーにしてね、筆者はやらん。

.vscode/tasks.json は以下のように。

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Start Comipoli",
            "type": "shell",
            "command": "gjs",
            "args": [
                "./comipoli"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "presentation": {
                "focus": true,
                "reveal": "always",
                "panel": "new"
            }
        }
    ]
}


こうしておけば部品ファイルを開いている状態でも実行できて便利。
下に出るコンソールから Ctrl+C で強制終了もできる。
終了したら何かキーを叩けばコンソールは終了してくれる。

後はスニペットをガシガシ登録。
Ctrl+Space を先に打ち込む必要があるのがメンドイぞ!

{
	 "_connect": {
		"prefix": "con",
		"body": [
			"${1:Widget}.connect(\"${2:SignalName}\", ($3)=> {",
            "    $0",
            "});"
		],
		"description": "connect"
	},
	"_imports": {
		"prefix": "imp",
		"body": [
			"const ${1:GLib} = imports.gi.${1};$0"
		],
		"description": "imports"
	}
}

って。
おいおい Gedit より便利じゃなったじゃないか!
シェルスクリプトだけで拡張できる Gedit よりはるかに面倒だが。

でも vscode って思いっきり拡張子依存なのよね。
拡張子が無いファイルを扱うことが多い Linux ではチト困る。
それを補う Gedit のモードラインが再現できればなぁって感じ。

unzip escape

comipoli を gjs 化するので python3 の zipfile が使えない。
ということで unzip コマンドを使う方法に切り替えるのだが
展開する内部ファイル名にブラケット [, ] があると展開できないと気が付いた。

検索するとアスタリスクが駄目なことは皆気がついているようだ、他にもあるかな?
file-roller の GPL ソースを見るのが一番速い。

file-roller/fr-command-zip.c at master ? GNOME/file-roller ? GitHub

[]*?!^-\

こんなにあった、てか記号全部ではないのね。
駄目文字の前にバックスラッシュを付けるだけだし変換は簡単だね。

var ComipoliArchive = class ComipoliArchive {
    // etc...
    _zipEscape(str) {
        const ESCAPE = "[]*?!^-\\";
        let res = "";
        for (let s of str) {
            if (ESCAPE.includes(s)) res += "\\";
            res += s;
        }
        return res;
    }
    getItem(num) {
        let cmdArray = null;
        let pixbuf = null;
        switch (this.status) {
        case 1:
            cmdArray = ["unrar", "p", "-inul", "-@", "--", this.path, this.namelist[num]];
            break;
        case 2:
            cmdArray = ["7za", "x", "-so", this.path, this.namelist[num]];
            break;
        default:
            //cmdArray = ["unzip", "-pj", this.path, this.namelist[num]];
            cmdArray = ["unzip", "-pj", this.path, this._zipEscape(this.namelist[num])];
        }
        let sp = Gio.Subprocess.new(cmdArray, Gio.SubprocessFlags.STDOUT_PIPE);
        let stream = sp.get_stdout_pipe();
        try {
            pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
        }
        catch(e) {
            print("Error: " + this.namelist[num] + "\n" + e);
        }
        stream.close(null);
        return pixbuf;
    }
}

展開できた。

しかし cairo 描写に書き換えたら 3D で動く毎に draw シグナルが…
これでは clutter_actor_save_easing_state が使えないや。

yield @ Python3 and JavaScript

我が comipoli はどうせ cairo に書き換えるなら Gjs 化してしまおう。
ということで以前 ClutterImage 作成が遅過ぎでお蔵入りしたソースを引っ張りだした。
ES6 仕様に書き換えをしていて yield でハマったので覚書。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib

class Dlg(Gtk.Dialog):
    def __init__(self):
        Gtk.Dialog.__init__(self, transient_for=None, title="Comipoli Thumbnail")
        self.show_all()
        # idle
        self.iter = self.iter_func()
        GLib.idle_add(self.iter_idle)

    def iter_idle(self):
        try:
            n = next(self.iter)
            print(n)
            return True
        except Exception as e:
            return False

    def iter_func(self):
        for num in range(100):
            yield num

dlg = Dlg()
dlg.run()
dlg.destroy()

PyGObject

#!/usr/bin/gjs

const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;

var Dlg = GObject.registerClass({
    GTypeName: "Dlg"
}, class Dlg extends Gtk.Dialog {
    _init() {
        super._init({
            transient_for: null,
            title: "Comipoli Thumbnail"
        });
        this.show_all();
        // idle
        this.iter = this.iter_func();
        GLib.idle_add(GLib.PRIORITY_LOW, ()=> {
            let r = this.iter.next();
            if (r.done) return false;
            print(r.value);
            return true;
        });
    }
    * iter_func() {
        for (let i=0; i<100; i++) {
            yield i;
        }
    }
});
Gtk.init(null);
let dlg = new Dlg();
dlg.run();
dlg.destroy();

Gjs

g_idle_add の引数が違うことは置いておいて。
g_idle_add は何も処理を行っていない時に与えられた関数を実行し続ける関数。
false を戻さないかぎり動き続ける。

Python は例外で判別、JavaScript は next メソッドの戻り値で判別。
yield, next の使い方はまったく同じなので混乱してもーたがね。

検索すると個別で next() を呼んでいる人ばかりで困ったよ。
どう考えても yield でそんな使い方はしないと思うんですけど。
複数ファイル処理で一つ読み終わったらとっとと表示みたいな場合が主だよね。

あと JavaScript のジェネレーター関数はアスタリスクが必要。
無名関数にする手段は無いっぽい、JavaScript を筆者が使うようになった切っ掛けなのに。
でも Python には無名関数はいらないな、そんなの Python じゃない。
適材適所ってことですよ、何でもアリはコードを読み辛くするだけだ。

Gjs 1.50 GtkWindow

GitHub の gjs サンプルコードが更新されていることに気が付いた。

gjs/gtk-application.js at master ? GNOME/gjs ? GitHub

あれ、_init というメソッドが定義されていたの?
constructor を無理に使わなくてもいいってことみたい。
_init なら以前書いたみたく super() 以降を別関数に分けなくてもイケるのかな。

GtkApplication を使うなら Gtk.init() は不要だった。
実は Y901x を作っていた時に気がついていた、コッチに書くのを忘れていた。

constructor の引数は JSON しか受け付けなかったけど _init だとどうだ?
わかんない時はとっととサンプルコードを書いて動かすほうが速いので早速。
単なる Window ではつまらないので Evince のバインドで。

#!/usr/bin/gjs

imports.gi.versions.Gtk = "3.0";
imports.gi.versions.EvinceDocument = "3.0";
imports.gi.versions.EvinceView = "3.0";

const Gtk = imports.gi.Gtk;
const Gdk = imports.gi.Gdk;
const GObject = imports.gi.GObject;
const EvinceDocument = imports.gi.EvinceDocument;
const EvinceView = imports.gi.EvinceView;

var EvinceWindow = GObject.registerClass({
    GTypeName: "EvinceWindow"
}, class EvinceWindow extends Gtk.Window {
    /*constructor(props={}) {
        super(props);
    }
    create() {*/
    _init(text) {
        super._init({title: text});
        this.model = new EvinceView.DocumentModel();
        let view = new EvinceView.View();
        view.set_model(this.model);
        // Scroll
        let scroll = new Gtk.ScrolledWindow();
        scroll.add(view);
        this.add(scroll);
        /* No need
        this.drag_dest_set(
            Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
            [Gtk.TargetEntry.new("text/uri-list", 0, 0)],
            Gdk.DragAction.MOVE
        );*/
        this.drag_dest_add_uri_targets();
        this.connect("drag-data-received", (widget, drag_context, x, y, data, info, time)=> {
            let uri = data.get_uris()[0];
            let doc = EvinceDocument.Document.factory_get_document(uri);
            this.model.set_document(doc);
        });
        //this.connect("hide", Gtk.main_quit); //arguments Error
        this.connect("hide", ()=>Gtk.main_quit());
        this.show_all();
    }
});

// When GtkApplication is not used.
Gtk.init(null);

EvinceDocument.init();

let ev = new EvinceWindow("Drop the PDF File!");
//ev.create();
Gtk.main();

なるほど、_init を使うほうが圧倒的に簡単になるやん。
ちなみに gir で Evince 等を使うドキュメントは以下に。
GNOME JavaScript Docs

それより drag_dest_set 指定はいつのまに不要になったのだ?
以前は drag_dest_add_uri_targets だけでは動作しなかった気がするんだが。

jjs Timer

JavaScript 関連で検索していると大半が
setInterval, setTimeout
を使っているんだけど、これってウエブブラウザと Node.js しか使えない。

Gjs で使う手段は筆者は PyGObject の経験ですぐ解る。
JXA も Objectibe-C から書き換えるだけなので簡単。
問題は jjs だ、情報が無さすぎる。

TimerTask (Java Platform SE 8 )

どうやらこのクラスを使うしかなさそうなんだけど。。。。。
jjs でクラスメソッドのオーバーライドってどうやるんだ?

SOAT Blog ? Java 8 ? What’s new ? ? 3/3 ? Type Annotations et Nashorn

こんなブログ見つけた!
なるほど、コンストラクタの後ろに JSON で指定ね。
なんか変なので括弧の内側に書いても同様に動いた、相変わらずいいかげんな言語だ。
Gjs, JXA と似た感じになるので内側推奨。

サンプルコードを試すと動いたけど Ctrl_C を使わないと終了しないじゃないの。
多分 Timer クラスの cancel メソッドを呼ばないと終了できないんだろう。
sleep を使っているのも気に入らない、書き換えだ。

#!/usr/bin/env jjs

var count = 1;
var timer = new java.util.Timer();
var task = new java.util.TimerTask({
    run: function() {
        if (count == 5) {
            task.cancel();
            timer.cancel();
        }
        print(count);
        count++;
    }
});
timer.schedule(task, 0, 1000);
print("__EOL__");

OK!

どういうわけか timer を task の前に定義しないと動かなかった。
__EOL__ まで抜けているので参照できるはずなんだけど多分仕様だろう。

つか、JXA は全然簡単じゃなかったワイ!
jxa – setTimout with Javascript for automation – Stack Overflow

JXA はなるべく解りやすいよう書き換えて、まとめてみた。
Gjs, jjs, JXA, Node.js – L’Isola di Niente

てなことで。
あけましておめでとうございます。