JavaScript」タグアーカイブ

Gjs is JavaScript

最近知ったけどガールフレンド(仮)というスマホゲームって普通にスマホのブラウザ上で動くブラウザ版ってのがあるんだね。
他にも同様なものがあるらしい、つまり HTML5 と JavaScript 製なのか。
スマホアプリは Objective-C, JAVA しかないと思っていたよ。

そういえば Google Chrome エクステンションも JavaScript だ。

ところで筆者のパソコンは Fedora で GNOME を愛用している。
何を今頃と思われそうだけど gnome-documents って Gjs 製なんだね。

gnome_documents

こんなところにも JavaScript が。
知らぬ間にあらゆるものが JavaScript になっていってる。

もう毛嫌いしている場合じゃない、普通に使えるくらいにはならないと。

ということで久々に Gjs を。
Seed プロジェクト側はヤル気が無いみたいだし。

http://ftp.gnome.org/pub/GNOME/sources/seed/
http://ftp.gnome.org/pub/GNOME/sources/gjs/

二年くらい前に少し触った頃と随分変わった気がする。
単に情報が色々出て来ただけだが、やはり日本語情報は皆無に近いけど。
とりあえず gnome-documents のコードを参考にしてウインドウを作ってみる。

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

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

    _init: function(app) {
        this.parent({
                application: app,
                width_request: 300,
                height_request: 200,
                title: "TitleBar" });
        // or let button = Gtk.Button.new_with_label("Click!");
        let button = new Gtk.Button({ label: "Click!" });
        // or button.connect('clicked', Lang.bind(this, this._onButtonClicked));
        button.connect('clicked', function() {
            button.set_label("DDDone!");
        });
        this.add(button);
        this.show_all();
    },
    /*_onButtonClicked: function (button) {
        button.set_label("Done!");
    }*/
});

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

    _init: function() {
        // TaskBar Title
        GLib.set_application_name("Test");
        // property
        this.parent({
                application_id: 'apps.test.Test',
                flags: Gio.ApplicationFlags.FLAGS_NONE });
    },
    // override activate signal
    vfunc_activate: function() {
        new TestWindow(this);
    }
});
let application = new Application();
application.run(ARGV);

GtkApplication は Gjs からも使えるようになったみたい。
というか gnome-documents が普通に使っていた。

class みたいに扱う時 Seed は GType、Gjs は Lang.Class。
Name 指定は必須、継承元は Extends にて指定、コンストラクタが _init ね。
なんとも奇妙だが JavaScript 仕様に class は無いので慣れるしかない。

プロパティセットは Python が __init__ で Gjs が parent にて可能。
普通にドットから代入でもいいけど。

# Python
self.props.title = "new Title"
// Gjs
this.title = "New Title";

シグナルハンドラは Vala のように無名関数が利用できる。
connect でもいいんだけど何故こんなに面倒な書き方なんだろう。
Python でも使えるといい気がするけどインデントが台無しになるお。

オーバーライドは Python が do_ で Gjs が vfunc_ のようだ。
無駄な connect を書かずにすむのは嬉しいね。

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

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

    _init: function() {
        this.parent({ title: "TitleBar" });
        this.show_all();
    },
    vfunc_delete_event: function() {
        Gtk.main_quit();
    }
});
// Initialize the gtk
Gtk.init(null, 0);
new TestWindow();
Gtk.main();

GtkApplication 使用時は不要だけど Gtk.main では init() 必須。
というより Python だと init 呼び出し不要なのが変ということだが。

Gtk.Button.new_with_label が使えたのは何故か不思議な感じ。
そういえば JavaScript は何でもアリだったな。

こんなことをやっても Web やスマホアプリを作りたい時の参考にならない気も。
とはいえまったく文法を知らないよりはマシだからもう少し続けるかも。

Here Document

ヒアドキュメントが便利なのでもう少し調べる。
Y901x のインストールスクリプトで使っているから経験はあるのだが。

ヒアドキュメント – Wikipedia

シェルスクリプトはバッククォーテーションでコマンドも使えるのか。

#!/bin/sh

func() {
    echo バッククオートで関数も使える
}
shstr=シェルスクリプト
hdstr=ヒアドキュメント

cat << __EOS__
${shstr}で$hdstrは今更だけど
最初の改行は無視される、最後は echo や cat が改行する
エスケープ文字 \\ は有効
記号は\$とバッククオートを除いて <'"{[+=~ と普通に使える
`func`
__EOS__

自作関数でもいいようだ。

# output
シェルスクリプトでヒアドキュメントは今更だけど
最初の改行は無視される、最後は echo や cat が改行する
エスケープ文字 \ は有効
記号は$とバッククオートを除いて <'"{[+=~ と普通に使える
バッククオートで関数も使える

これはもしかして PHP でも関数が使えるかも。

<?php

function func() { return "関数"; }
$plstr = "Perl";
$shstr = "シェルスクリプト";

echo <<< __EOS__
PHP は${shstr}や $plstr 同様に利用できる
最初と最後の改行は無視されるので最後は一行開けるとよし
エスケープ文字 \\ は有効
記号は\$を除いて <'"{[+=~` と普通に使える
`func` って PHP にこんな機能はもともと無いよ!

__EOS__;
/* output
PHP はシェルスクリプトや Perl 同様に利用できる
最初と最後の改行は無視されるので最後は一行開けるとよし
エスケープ文字 \ は有効
記号は$を除いて <'"{[+=~` と普通に使える
`func` って PHP にこんな機能はもともと無いよ!
*/
?>

んなわけないか。
ところで PHP のヒアドキュメントは最後を改行しなくて最初戸惑った。
よく考えたら echo, cat, print() が改行していただけだった。
PHP の echo は改行しないもんね。

Perl や Lua って Fedora に最初から入っているけど使ったことが無いな。
余程のことがないかぎり今後も使うことは無いと思うけど。
GNOME は以前 JavaScript を押していたけど今はどうなんだろう?

// Gjs
// imports /usr/share/gjs-1.0/format.js

const Format = imports.format;

String.prototype.format = Format.format;

let jstr = "\
JavaScript はエスケープで強引な改行しか手段が無い\n\
更に%s機能は無い\n\
しかし %s ならこんなことができる\n\
%%s, %%d, %%x, %%f のみ";

print(jstr.format("文字列フォーマット", "Gjs"));
/*
JavaScript はエスケープで強引な改行しか手段が無い
更に文字列フォーマット機能は無い
しかし Gjs ならこんなことができる
%s, %d, %x, %f のみ
*/

これだものな。
面倒臭くなってブン投げたのは筆者だけではないと思う。

#include <stdio.h>

#define CSTR "\
%s は\n\
説明不要だよね\n"

int
main (int argc, char ** argv) {
	printf (CSTR, "C 言語");
	return 0;
}
/*
C 言語 は
説明不要だよね
*/

C のほうが簡単なんて洒落にもならん。
プラス記号で成形するのって最初は分かり易いのでいいと思うが。

#!/usr/bin/env python3

DOCSTR = """{0} はお馴染 {1}
最初と最後の改行も有効だけど print が最後を改行する
{2}
{1} は \\ エスケープが有効"""

def func():
    return "関数は format で実行すれば文字列さ"

print(DOCSTR.format("Python", "docstring", func()))

''' output
Python はお馴染 docstring
最初と最後の改行も有効だけど print が最後を改行する
関数は format で実行すれば文字列さ
docstring は \ エスケープが有効
'''

あぁ楽チン、やっぱりコレだよコレ!

しかしやっぱりヒアドキュメントに変数を直書きできたほうが便利。
それには $ 記号を変数に利用する言語しか無理なんだろうな。
と思っていました。

// Vala

const string DOCSTR = """Python と同じ
%s も使える、でも ''' は使えない
何故か \\ エスケープ\nは使えない
""";

string func() { return "無理"; }

int main (string[] args) {
    string one = "Vala";
    string tow = "ヒアドキュメント";
    stdout.printf(@"$one は実は@\"\"を使って$towもどきが利用できる
ですが$${one}みたいなブレース表記はダメみたい
最初と最後の改行も有効、エスケープ文字 \\ は有効
記号は$$と\"を除いて <'{[+=~` と普通に使える
`func` は当然不可能\n\n");
    //
    stdout.printf(DOCSTR, "docstring");
    return 0;
}
/* output
Vala は実は@""を使ってヒアドキュメントもどきが利用できる
ですが${one}みたいなブレース表記はダメみたい
最初と最後の改行も有効、エスケープ文字 \ は有効
記号は$と"を除いて <'{[+=~` と普通に使える
`func` は当然不可能

Python と同じ
docstring も使える、でも ''' は使えない
何故か \\ エスケープ\nは使えない
*/

Vala はやってくれました。
おかげで解った、滅茶苦茶使い辛いということを。
これなら docstring 方式のほうがいいや。

最後に、Gedit の色分けってスゴすぎる!

gedit

まさかこの Vala の $ 変数を見分けて色分けするとは思わなかった。
PHP 部分と HTML 部分をしっかり見分けるとかは知っていたが。
どの言語でもヒアドキュメント内でマズい記号を打つと即座に色が変わる。
これがデフォルトエディタって GNOME 恐るべしだよ。

WebKit Seed imports

GNOME の JavaScript 実装は seed と gjs がある。

JavaScript – GNOME Live!

seed は WebKit の Javascript エンジン。
gjs は Spidermonkey(Firefox) の Javascript エンジン。

であるようだ。
一応書くと Google Chrome のエンジンは V8 なので違います。
ようするに普通に WebKit を GNOME で使うと Seed を利用するということかな。

Python with GTK+3 WebKit Browser 2 | PaePoi
試しに以前作った上記と Google Chrome で V8 ベンチをやってみる。
V8 Benchmark Suite

V8 はやはり早い、圧倒的な差があるのね。
まあそれはよくて、Seed なら imports が使えるはず。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>GtkWindow</title>

<script>
function test() {
    try {
        var Gtk = imports.gi.Gtk;
    }
    catch(e) {
        alert(e);
    }
}
</script>

</head>
<body>
<input type="button" onclick="test()" value="Test">
</body>
</html>

ダメじゃん、ということで。

Blogging in the wind: WebKit: Extending Javascript – Seed (V)

こんなページを見つけた。
これは面白そうだ、ヘッダを揃えてビルドしてみよう。
webkitgtk-devel, seed-devel を入れればいいかな。

環境が揃ったのでビルド、問題なく実行ファイルが作成された。
ということで、GtkWindow を作成する HTML5 ファイルを読み込ませてみる。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>GtkWindow</title>

<script>
var on_clicked = function() {
    //
};
function create() {
    try {
        var Gtk = imports.gi.Gtk;
        Gtk.init (null, null);
        var gtkwin = new Gtk.Window();
        gtkwin.signal.hide.connect(Gtk.main_quit);
        var label = new Gtk.Label({label:"Button Click -> WARNING"});
        var button = new Gtk.Button({label:"Button Clicked"});
        button.signal.clicked.connect(on_clicked);
        var vbox = new Gtk.VBox();
        vbox.pack_start(label);
        vbox.pack_start(button);
        gtkwin.add(vbox);
        gtkwin.show_all();
        Gtk.main()
    }
    catch(e) {
        alert(e);
    }
}
</script>

</head>

<body>
<div>First time an error</div>
<input type="button" onclick="create()" value="Do GtkWindow">
</body>
</html>

ボタンの初回クリックで例外を吐く。
けど2回めからは問題なく GtkWindow が作成された。
body.onLoad() で imports なんかも試してみたが同じだった。

それよりシグナルのハンドラがうまくいかない。
RangeError って意味がよく解らないんですけど…
Seed の例外は何が悪いのかが PyGI より理解し辛いのが難点だよな。

うーイマイチだ。
でもとにかくコレで WebKit から imports が使えることは解った。
利用場面があるかどうかは別の話として。

G_APPLICATION_HANDLES_OPEN @ C, PyGI, Seed

PyGI で GtkApplication の G_APPLICATION_HANDLES_OPEN が上手くいかない。
引数の有る無しで activate か open シグナルに振り分けされる処理だが…
引数を渡せば open のシグナルが来るのだけど files は空ッポの配列になる。
もしかしてと思い Seed で使ってみたがコレも上手く行かない。

GtkApplication

bloat_pad とかいうサンプルがあるのに…
まてよ、C 言語でなら本当に上手くいくのだろうか?
C で GTK+ をやるつもりは無かったけど GTK+ ヘッダを入れて実験してみよう。

ついでだから Fedora 16 で GTK+ ヘッダ導入方法を少し。

gcc は誰でも解るからいいとして、とうの昔に私は導入済みだし。
「追加/削除」にて gtk3 で探せばアッサリヘッダ群は見つかる。

gtk-devel* だけチェックすれば cairo とかのヘッダも勝手に入る。

devhelp も入れておくと web でマニュアルを読むより楽。
anjuta は個人的にはいらない、IDE の使い方を覚えるのと Makefile の書き方を覚えるのとでどちらの時間がもったいないと思うかで決めればいいと思う。

さて準備はできた、Makefile を作って bloat_pad とやらをビルド。
あたりまえのように files には GFile のリストが入っていた。

でもこのコードではイマイチ理解ができないので単純なコードで作ってみる。
C は普通に g_signal_connect させてみた。

#include <gtk/gtk.h>

static void
newwin (GApplication *app,
        const gchar  *title)
{
    GtkWidget *window;

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_application (GTK_WINDOW (window), GTK_APPLICATION (app));
    gtk_window_set_title(GTK_WINDOW (window), title);
    gtk_window_resize(GTK_WINDOW (window), 200, 10);
    gtk_widget_show_all (GTK_WIDGET (window));
}

static void
on_activate (GApplication *application)
{
    newwin (application, "activate");
}

static void
on_open (GApplication  *application,
         GFile        **files,
         gint           n_files,
         const gchar   *hint)
{
    gint i;

    newwin (application, "open");

    for (i = 0; i < n_files; i++)
        printf ("%s\n", g_file_get_uri(files[i]));
}

int
main (int argc, char **argv)
{
    gtk_init (NULL, NULL);
    GtkApplication *app;
    int status;

    app = gtk_application_new ("org.gtk.apptest",
            G_APPLICATION_HANDLES_OPEN);
    g_signal_connect(G_OBJECT(app), "activate",
            G_CALLBACK (on_activate), NULL);
    g_signal_connect(G_OBJECT(app), "open",
            G_CALLBACK (on_open), NULL);

    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);

    return status;
}

WARNING で Files==[] になる PyGI
sys.argv をタプルにしてみたが結果は同じだった…

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

import sys
from gi.repository import Gtk, Gio

class Win(Gtk.Window):
    def __init__(self, app, title):
        Gtk.Window.__init__(self)
        self.set_application(app)
        self.set_title(title)
        self.resize(200, 10)
        self.show_all()

def on_activate(application):
    Win(application, "activate")

def on_open(application, files, n_file, hint):
    print files
    Win(application, "open")
    """
    for f in files:
        print f.get_uri()

    Warning: g_value_get_boxed: assertion `G_VALUE_HOLDS_BOXED (value)' failed
    """

Gtk.init(sys.argv)
app = Gtk.Application.new(
        application_id="apps.test.helloworld",
        flags=Gio.ApplicationFlags.HANDLES_OPEN )
app.connect("activate", on_activate)
app.connect("open", on_open)
ARGV = tuple(sys.argv)
app.run(ARGV)

WARNING は出ないけど files がワケワカになる Seed
Seed は set_title しなくてもコレでタイトルは指定できるよ。
それと Seed.argv は配列を一つ減らさないと認識してくれなかった。

#!/usr/bin/env seed

Gio = imports.gi.Gio;
Gtk = imports.gi.Gtk;

Win = new GType({
    parent: Gtk.Window.type,
    name: "SeedWindow",
    init: function (title){
        this.resize(200, 10);
        this.show_all();
    }
});

Gtk.init (null, null);

var app = new Gtk.Application({
    application_id:"apps.test.app",
    flags:Gio.ApplicationFlags.HANDLES_OPEN
});
app.signal.activate.connect( function(application) {
    var w = new Win({title:"activate"});
    w.set_application(application);
});
app.signal.open.connect( function(application, files, n_file, hint) {
    print(files);
    var w = new Win({title:"open"});
    w.set_application(application);
    /*
    for (var i=0; i<n_file; i++) {
        print( files[i].get_uri() );
    }
    TypeError 'undefined' is not an object (evaluating 'files[i].get_uri')
    */
});
var r = new Array();
for (var i=1; i<Seed.argv.length; i++)
    r.push(Seed.argv[i]);
app.run(r.length, r);

Makefile 入りアーカイイブも置いておく。
Wordpress って tar.xz だとアップロードができないじゃん!
まあいい、tar.gz で。

app.tar.gz

実行、引数を入れればドレもタイトルバーが open になるのが解る。
でも上手くいくのは C のみ、Seed はどうにかできそうだが今はワカンネ。
Python は PyGI の処理なのか、gi 自体の限界なのか…

ついでに解ったけど存在しないファイル名でも files に含まれるのね。

てなわけで、現状では HANDLES_OPEN にしたいなら C でやれってことですね。
しかし久々の C 言語はやっぱり面倒くさい。

Gio Streaming I/O @ Seed and PyGI

そういえば Seed で環境変数を使うにはどうすればいいのだろう?
Python のように便利な標準モジュールみたいなのは無いわけで。
多分 glib 等に頼るしかないのだろう。

Miscellaneous Utility Functions

glib に env 関連はあるね。

#!/usr/bin/env seed

GLib = imports.gi.GLib;

Seed.print(GLib.getenv("HOME"))

GLib.setenv("HOGE", "Madoka Magica")
Seed.print(GLib.getenv("HOGE"))

これでイケる、GLib.get_home_dir() でも $HOME は取れる。
でも日本語を getenv だと ? になって print されてしまった。

インタラクティブシェルなら問題なく日本語表示している。
? の文字数は合っているから UTF-8 だと認識はしているみたいだが何故だろう。
Python の coding:utf-8 指定のようなものが何か必要なのか?

そういえばファイルを読み書きする open() も無いよな。
gio で読み書きするしか方法が無いと思うけど。

Seed/Tutorial – GNOME Live!
Seed/Tutorial/Simple_file_io – GNOME Live!

GDataInputStream, GDataOutputStream を噛ます必要があるようです。
Streaming I/O
なんか面倒くさいけど以下で読み書きできた。
読み込みはよくある一行毎に処理する方法にしている。

#!/usr/bin/env seed

/*
    Streaming I/O File Read and Write Sample
    if the Seed
    ( null parameter is not needed )
*/

Gio = imports.gi.Gio;

var filename = "test_js.txt";
var lines = "madoka\nhomura\nmami\nsayaka\nkyoko";

// Write
var f = Gio.file_new_for_path(filename);
var fstream = f.replace();
var dstream = new Gio.DataOutputStream.c_new(fstream);
dstream.put_string(lines);
fstream.close();

// Read
f = Gio.file_new_for_path(filename);
fstream = f.read();
dstream = new Gio.DataInputStream.c_new(fstream);
while (1) {
    var text = dstream.read_line_utf8();
    if (text == null) break;
    Seed.printf("%s(%d)", text, text.length)
}
fstream.close();

日本語でも書き込みはできた、けれどアウトプットはやはり ? になる。
それと length を取得する方法が無いみたい、int では値渡しになるし…

コレで gi にて Streaming I/O を使う方法が解ったぞと。
ということは PyGI で同様にするには以下のように。
ほぼ同じだったけど微妙に違う。

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

'''
    Streaming I/O File Read and Write Sample
    if the Python
    ( All None parameter @ GCancellable )
'''

from gi.repository import Gio

filename = "test_py.txt"
lines = "madoka\nhomura\nmami\nsayaka\nkyoko"

# Write
f = Gio.file_new_for_path(filename)
fstream = f.replace("", False, Gio.FileCreateFlags.NONE, None)
dstream = Gio.DataOutputStream.new(fstream)
dstream.put_string(lines, None)
fstream.close(None)

# Read
f = Gio.file_new_for_path(filename)
fstream = f.read(None)
dstream = Gio.DataInputStream.new(fstream)
while 1:
    text, length = dstream.read_line_utf8(None)
    if text == None:
        break
    print "{0}({1})".format(text, length)
fstream.close(None)

Seed は基本的に NULL でいい引数は書く必要は無い、書いても結果は同じ。
Python は GCancellable を強要する、None で通常なら問題ない。
つか Python はやっぱりタプルを戻す、つまり Python なら length も取れる。
慣れていないと困惑するよなこの仕様。

DataInputStream の c_new って何だろう?

#!/usr/bin/env seed

Gio = imports.gi.Gio;

for (var s in Gio.DataInputStream) {
    Seed.print(s);
}
/* output
type
c_new
prototype
*/

static 状態では prototype のメソッドを使えないということか。
そういうことなら new という名前でイイと思うが理由があるのだろう。

とりあえずこれだけ解れば小物スクリプト程度なら作れると思う。

どうでもいいけど。

var f = Gio.File.new_for_path(fn)
var f = Gio.file_new_for_path(fn)

PyGI でも同じである、gi ってどっちでもいいんだね。