Programming」カテゴリーアーカイブ

ES6 Promise

ES6 には Promise という非同期関数がある。

Promiseと仲良くなって気持ち良く非同期処理を書こう – Qiita

なるほど、Promise 関数と then 関数を使えばいいのか。
てなわけで、毎度のようにコイツで GUI アプリを起動してみよう。

#!/usr/bin/gjs

const GLib = imports.gi.GLib;

function asyncFunction(guiApp) {
    return new Promise(function (resolve, reject) {
        print("__DO__");
        GLib.spawn_command_line_sync(guiApp);
        resolve("__DONE__");
    });
}

asyncFunction("eog")
    .then(function (value) {
        print(value);
    })
    .catch(function (error) {
        print(error);
    });

print("__EOL__");

だめヤン!
非同期ならこの状態で __EOL__ まで抜けているはずだ。

で、昨日気が付いたんだけど思い込みによる勘違いだった。
非同期になるのは Promise 関数内ではなく then 関数内であるようだ。

#!/usr/bin/gjs

const GLib = imports.gi.GLib;

function asyncFunction() {
    return new Promise(function (resolve, reject) {
        resolve(null);
    });
}

print("__START__");

asyncFunction()
    .then(function (value) {
        GLib.spawn_command_line_sync("eog");
    });

print("__EOL__");

コレなら __EOL__ まで抜けているのにアプリは実行されたままになる。
GUI アプリを起動するコードだと動作を把握しやすい、Linux の長所。

んで、実は単純な非同期処理なら省略表記があって。

#!/usr/bin/gjs

const GLib = imports.gi.GLib;

Promise.resolve(null).then(function (value) {
    GLib.spawn_command_line_sync("eog");
});

print("__EOL__");

以上。

超シンプルな非同期処理のできあがり。
Promise 関数は前処理で then が非同期の実行という解釈でいいかな。
前処理(確認作業)が不要なら省略表記で、と使い分けできるね。

アロー関数を使えばクラスメソッドも問題なく使えるようだ。

#!/usr/bin/gjs

const GLib = imports.gi.GLib;

class PromiseTest {
    get command() {return this._command;}
    set command(cmd) {this._command = cmd;}
    asyncFunc() {
        Promise.resolve(null).then((value)=> {
            GLib.spawn_command_line_sync(this.command);
        });
    }
}

let test = new PromiseTest();
test.command = "evince";
test.asyncFunc();

print("__EOL__");

ES6 フル活用!

えぇ… JavaScript !!!
と記法で超嫌っていた JavaScript が今ではこんなに面白いとは予測できなかったYO!

GNOME 3.26 gjs class

おまたせ、gjs の ES6 class でアプリを作る方法がやっと解ったよ。
PyGObject みたく親切に例外を吐くなんてしてくれないので厳しい。

注意点、constructor はマジでコンストラクタです。
Python の __init__ 等と違う、動的言語でまさかこの仕様だとは。
細かい説明はしないけど、つまり super() 以外は何もできない。
継承で色々行う場合は下記のように別関数にする等の必要あり。

/*
const MyWindow = new Lang.Class({
    Name: 'MyWindow',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({application: app});
        this.createContents();
        // etc...
*/
var MyWindow = GObject.registerClass({
    GTypeName: "MyWindow",
}, class MyWindow extends Gtk.ApplicationWindow {
    constructor(props={}) {
        super(props);
    }
    create() {
        this.createContents();
        // etc...

更に constructor の引数はベースへの property を JSON でに限定。
他を入れるとエラーになるのでとにかくこう書く以外に手段は無いみたい。

override で継承元の関数を呼ぶには super からドットで呼び出す。
Lang.Class ではないので this.parent() は使えない。

var MyApplication = GObject.registerClass({
    GTypeName: "MyApplication"
}, class MyApplication extends Gtk.Application {
    constructor(props={}) {
        super(props);
    }
    vfunc_startup() {
        //this.parent();
        super.vfunc_startup();

コレはすぐ気が付くか。

それ以外は今までのコードをそのまんま使えるようだ。
var にする必要があるのは外部から使う class だけでいいみたい。

本当は物凄い遠回りをしたんだけど以上であるようだ。

ということで、もう少し実験してから Y901x を更新する。
ぶっちゃけ動画プレイヤーは gnome-mpv でイイんだけど
Y901x は gjs のサンプルコードがメインの仕事だったことを忘れる所だった。

GNOME 3.26 gjs p2

散々探してやっと見つけた。

Inventing GObject ES6 classes | The Mad Scientist Review

なんかイッパイ手段があるみたい。
一番簡単そうな手段で試してみる。

/* btn.js */

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

var Btn = GObject.registerClass({
    GTypeName: "BtnClass"
}, class Btn extends Gtk.Button {
    constructor(props={}) {
        super(props);
    }
    // function
    set_button_label(txt) { this.label = txt; }
    // getter
    get prop() { return this.label }
});

#!/usr/bin/gjs

imports.gi.versions.Gtk = "3.0";
imports.searchPath.unshift('.');

const Gtk = imports.gi.Gtk;
const Btn = imports.btn;

class Test extends Gtk.Window {
    constructor() {
        super();
        this.connect("hide", ()=> {
            Gtk.main_quit();
        });
        this.ll = "カワイイ";
        var btn = new Btn.Btn({label: "睦ちゃん"});
        btn.connect("clicked", (widget)=> {
            // getter
            var s = widget.prop;
            // function
            widget.set_button_label(`${s} ${this.ll}`);
        });
        this.add(btn);
        this.show_all();
    }
}

Gtk.init(null);
let test = new Test();
Gtk.main();

で動かしてみる。

何も警告は出なくなった、思いっきり const を使っているのに。
これで安心してコード分割ができるぞい。
ただ、ちょっぴり面倒臭い。

関数定義や getter/setter は検索して一番解りやすかったページが以下。

JavaScriptにもクラスがやってきた!JavaScriptの新しいclass構文をマスターしよう | HTML5Experts.jp

コピペして console.log を print に書き換えてと。
うん gjs 1.50.2 で問題なく動くんだね。
だけど gir で継承すると…

#!/usr/bin/gjs

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

const Gtk = imports.gi.Gtk;
Gtk.init(null);

//* gir @ Error
class Btn extends Gtk.Button {
    constructor() { super(); }
    put() { print("func test"); }
}
//*/

/* no gir @ OK!
class Btn {
    constructor() {}
    put() { print("func test"); }
}
*/
var btn = new Btn();
btn.put();

何でや!

getter/setter も同様だった、理由が解らない。
上記のように GObject.registerClass をするしかないっぽい。
class が使えるようになって面倒になるとは思わなかったよ。

GNOME 3.26 gjs

GNOME 3.26 の目玉、gjs が ES6 フル対応になった。
これでで class が使えるぞ、Lang.Class の奇妙なコードとはおさらばじゃ。
んと、その前に。

#!/usr/bin/gjs

// 'gjs a.js 苗ちゃん'

const GLib = imports.gi.GLib;

for (let val of ARGV) {
    print(val);
    //let s = decodeURIComponent(escape(val));
    //print(s);
}

変わっていないや、まあいいか。
ClutterImage のセットが激遅なのもそのまんまだね。
ClutterImage PyGObject/Gjs | PaePoi

それはそれとして新機能だ。
GtkWindow を作ってみよう、やっぱり GUI だよね。

#!/usr/bin/gjs

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

const Gtk = imports.gi.Gtk;

class Test extends Gtk.Window {
    constructor() {
        super();
        this.connect("hide", ()=> {
            Gtk.main_quit();
        });
        this.show_all();
    }
}

Gtk.init(null);
let test = new Test();
Gtk.main();

これでウインドウを作るだけの最小限コードのようだ。

imports は PyGObject と同様にバージョン指定が無いと警告が出るようになった。
アロー関数を使えば Lang.bind を使わずにあの this の糞仕様を回避できる。
他の説明はいらないよね。

次はコード分割してみよう。

#!/usr/bin/gjs

imports.gi.versions.Gtk = "3.0";
imports.searchPath.unshift('.');

const Gtk = imports.gi.Gtk;
const Btn = imports.btn;

class Test extends Gtk.Window {
    constructor() {
        super();
        this.connect("hide", ()=> {
            Gtk.main_quit();
        });
        this.btn = new Btn.Btn("睦ちゃん");
        this.add(this.btn);
        this.show_all();
    }
}

Gtk.init(null);
let test = new Test();
Gtk.main();

var Gtk = imports.gi.Gtk;

class Btn extends Gtk.Button {
    constructor(lb) {
        super({
            label: lb
        });
    }
}

えーーーーー
let も const も定義していないお!
Java と違って同一ソースに複数の class が書けるのでこうすればいいけど。

#!/usr/bin/gjs

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

const Gtk = imports.gi.Gtk;

class Test extends Gtk.Window {
    constructor() {
        super();
        this.connect("hide", ()=> {
            Gtk.main_quit();
        });
        this.btn = new Btn("睦ちゃん");
        this.add(this.btn);
        this.show_all();
    }
}

class Btn extends Gtk.Button {
    constructor(lb) {
        super({
            label: lb
        });
    }
}

Gtk.init(null);
let test = new Test();
Gtk.main();

コードが長くなると…

モジュールとして継承 class は作ってはいけないってことかな?
まだ試したばかりだし他の方法はあるかもしれないけど。
なんか一気にテンションが下がった、PyGObject に戻るかなぁ。。。

GTK+ Cancel Long Keypress

GTK+ にてキーの長押しを判別する手段を発見。
GTK+ はキーの長押しを認識できないようで | PaePoi
こんなことを書いて 8 年もたってしまった、遅すぎるぞ俺!

何かキーを長押しした状態で event.time の値を見ると同一だった。
つまり長押しは最初に押した時間のまま延々とイベントが飛んでくるようだ。
とっととサンプルコードを書いたほうが解りやすいということで。

#!/usr/bin/env python3

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

class CancelLongKeypressWindow(Gtk.Window):
    """
        Wayland Only
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("hide", Gtk.main_quit)
        self.connect("key-press-event", self.on_key_press_event)
        self.long_keypress = 0
        self.show()

    def on_key_press_event(self, widget, event):
        #
        # Cancel Long Keypress
        #
        if self.long_keypress == event.time:
            return False
        self.long_keypress = event.time
        #
        print("Press!")
        return False

CancelLongKeypressWindow()
Gtk.main()

これでうっかり長押ししてしまっても早送りみたいな状態を避けられる。
逆に長押しされているのを判別するということも可能、こんなに簡単だった。
Comipoli で使うのと 8 年前の記事に合わせるため Python にしました。
これで我が Comipoli で早送りされてしまうのを防げるぞと。

そういえばあの頃は Ubuntu を使っていたなぁ。
今度から GNOME に戻るんだっけ、良さげなら又使うかな?
って GNOME に Dock を付けるという余計なことを又してる、ヤメた。
筆者は MacBook Air でも Dock はまったく使わないんですけど。
第493回 Ubuntu 17.10の変更点:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社

追記
Wayland 環境だけのようです。
X.org でログインしたら同様にならなかった。