JavaScript」タグアーカイブ

Gjs @ ARGV in CJK Filename

前々回の記事で Gjs 3.18 の ARGV は日本語未対応だと解った。
一応、Gjs は GTK+ 等と同様 GNOME の一部なのでバージョンは GNOME と同じ。
今後変わるかもしれないけど現状では各自で対策しなければ。

ただし単純に変換すればいいわけではない。
昔と違って今はパラメータが URI である場合もあるのだ。
それを吸収するため GtkApplication が作られたのかも。

function messagebox(text) {
    let dlg = new Gtk.MessageDialog({
        transient_for: null,
        modal: true,
        type: Gtk.MessageType.WARNING,
        buttons: Gtk.ButtonsType.OK,
        text: text
    });
    dlg.run();
    dlg.destroy();
};
ARGV.forEach(function(element) {
    messagebox(element);
});

とりあえずこんな処理を入れて実験。
Mac に日本語ファイル名の動画を置いて Fedora からダブルクリック。

mac_wclick

ダブルクリックだとマウント先が渡るのね。
そして日本語は見事に化けるという。

mac_afp

afp の URI で渡しても再生できる、http もいける。
そもそも URI の場合日本語等は既にエンコードされているはず。
ならばこうすればいいんでないの。

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

    _init: function() {
        this.parent({
            flags: Gio.ApplicationFlags.HANDLES_OPEN
        });
    },
    vfunc_open: function(files, hint) {
        let uri = files[0].get_uri();
        let w = new Y901Window(this);
        w.set_uri(uri);
    },
    vfunc_activate: function() {
        new Y901Window(this);
    }
});

let argv = [System.programInvocationName];
ARGV.forEach(function(element) {
    if (element.indexOf("//") == -1) {
        argv.push(decodeURIComponent(escape(element)));
    } else {
        argv.push(element);
    }
});
let application = new Y901Application();
application.run(argv)

“//” があれば URI 確定なのでそのまんま。
パス名なら日本語ファイル名かもしれないから変換処理。
で argv を作り直して g_application_run の引数にする。

これで現状何も問題なく使えている。
てゆーか、ARGV の仕様をなんとかしてくれないかなぁ。

Gjs Application HANDLES_OPEN

Gjs にて C 言語の __FILE__、Python の __file__ に相当するもの。
const System = imports.system;
System.programInvocationName;

javascript – Handle files passed as arguments on the command line in a Gjs Gtk.Application – Stack Overflow

なんだあったのか。
これでちょっぴりコードがスッキリできるぞ。

それと open シグナルの引数は二つだった。
引数や戻り値の数を間違って指定してもエラーにならない仕様ってチト困るよ。

Gio.Application::open

それらはそれほど問題にならないのだけれども。
コレはどうにかならないものか…

#!/usr/bin/gjs

const Lang = imports.lang;
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const System = imports.system;

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

    _init: function() {
        this.parent({
            // set G_APPLICATION_HANDLES_OPEN
            flags: Gio.ApplicationFlags.HANDLES_OPEN
        });
    },
    vfunc_open: function(files, hint) {
        // Not compatible CJK
        print(files[0].get_path());
        // to JavaScript String
        let jstr = decodeURIComponent(escape(files[0].get_path()));
        print(jstr);
    },
    vfunc_activate: function() {
        //
    }
});

let app = new OpenApplication();
ARGV.unshift(System.programInvocationName)
app.run(ARGV)

ちなみに PyGObject

#!/usr/bin/env python3

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

class OpenApplication(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self,
            flags=Gio.ApplicationFlags.HANDLES_OPEN )

    def do_open(self, files, n_file, hint):
        print(files[0].get_path())

    def do_activate(self):
        pass

app = OpenApplication()
app.run(sys.argv)

gjs_handles_open

アルファベットなら問題ないけど CJK だと引数の GioFile がおかしい。
path が滅茶苦茶な状態で渡されてくるので自力変換する必要あり。
変換方法はココで見つけたけどアルファベットは問題ないし UTF-16 じゃなくね?

javascriptで文字コード変換 – Qiita

get_uri でも同じ、一度変換して再変換という手間が必要。
PyGObject は問題ないのでバインディングの失敗ですよね。

# 追記
ARGV 自体がおかしかった。

ARGV.forEach(function(element) {
    print(element);
});
/*
gjs test.js 日本語.txt
#=>æ?æ?¬èª?.txt
*/

Gtk TreeView (Gjs, PyGObject)

Gjs で ListView(TreeView) にてハマったので覚書。
作り方自体は以下のページ手順で問題ないです。

TreeView with ListStore (JavaScript)

問題は PyGObject のコードから移植する場合。
PyGObject は実は PyGtk プロジェクトの記法を沢山取り入れている。
そのため記述が簡単だけど C の手段から掛け離れている場合が多々ある。

gtk_liststore_append はこんなに別物

/*@ PyGtk and PyGObject
liststore.append([data1, data2])

@ C
GtkTreeIter* it;
gtk_list_store_append(liststore, it);
gtk_list_store_set(liststore, it, 0, data1, 1, data2, -1);
*/

// Gjs
let it = liststore.append();
liststore.set(it, [0, 1], [data1, data2]);

PyGObject, PyGtk ってこんなに省略さていたのか!
C で複数セットの説明は省略、Gjs はナルホドと思うバインド。
PyGtk 様は必ずセットで行うのだから、とまさかのココまで省略したっぽい。
そのまんま PyGObject に採用されてしまったということですね。

gtk_tree_selection_get_selected なんかも。

/* PyGObject
model, it = selection.get_selected()
*/
let [isSelected, model, it] = selection.get_selected();

Python という言語は結果が同じ手段を一つしか提供しないことで有名。
上記は it が None かどうかを調べればいいので isSelected なんて不要。
憶測だけどそういうことなのだろうか。
しかも Gjs は上記で戻り値を二つにしてもエラーにならないから困る。

let [model, it] = selection.get_selected();
print(model);

みたく printf debug を駆使してやっとエラーの原因が解るの繰り返し。
Python コードから移植するより devhelp を見たほうが速い気がする。
複雑なツリービューだともっと面倒臭いかも。

そんなこんながありまして。
Y901x は本サイトでベータ公開となりました。

Linux アプリケーション – L’Isola di Niente

だって早く作らないと作った本人が拡大表示すらできない環境だもん。
SKYLAKE Fedora 環境は頗る安定しています。

array.forEach (this == array)

JavaScript は本当に奇妙な言語です。

#!/usr/bin/gjs

const Lang = imports.lang;

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

    _init: function() {

        this.arr = [12, 34, 56];
        this.str = "String";

        for (let i=0; i<this.arr.length; i++) {
            print(this.str);
            print(this.arr[i]);
        }
        this.arr.forEach(function(s) {
            print(this.str);
            print(s);
        });
    }
});
new TestClass();

/* output
String
12
String
34
String
56
undefined
12
undefined
34
undefined
56
*/

ループは forEach が解かり易いので多用していたけどコレにハマった。
Gjs はそもそも class が無い JavaScript にアクロバットな対応をしている。
this は C++ の this では無いと気が付くまで半日近く使ってしまった。

JavaScript の this は呼び出し元という意味。
つまり上記では forEach の this は this.arr である。
this.arr.str なんて宣言していないから undefined ということです。

foreach

こんな深い階層で this を使おうとした筆者が悪いんだけどさ。
Gjs だけではないので注意しようね。

Gjs Desktop Mascot

近頃は小ネタばかりでまともなサンプルコードを書いていないと気が付いた。
このブログらしくないので久々に小物アプリ作ってみます。

もちろん JavaScript(Gjs) で。
基本は大分やったと思うので実際に何か作ってみるとどうかを試したい。
経験で実際にアプリを作ってみないと気が付かないことが多いと知っている。

とりあえず今回はデスクトップマスコットを。
背景が透過されている PNG 画像をデスクトップに表示というありがちなもので。

常にすべての GUI アプリの再背面に表示とかイベントの処理とかのサンプルにも。
それなら応用範囲も広がるだろう、と勝手に考える。
何より重要なのが GtkApplication を必ず使う。
でないと後々で困ることになりそうな謎の雰囲気があるのですよ。
GNOME 公式サンプルからして。

ということで。
背景透過画像は以下からカワイイ苗ちゃんを拾って使う。

【ウチ姫】UR++「小粒姫 優木 苗」ステータス・評価まとめ – お姫さま図鑑|ウチ姫公式攻略Wiki – GAMY(ゲーミー)

んで、こんなコードになりました。

#!/usr/bin/gjs

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

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

    _init: function(app) {
        this.parent({
            application: app
        });
        // GdkColormap to GdkVisual
        let screen = this.get_screen();
        let visual = screen.get_rgba_visual();
        if (visual != null && screen.is_composited()) {
            this.set_visual(visual);
        } else {
            print("no Composited...");
        }
        this.set_app_paintable(true);
        // Transparent background image
        this.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size("nae_ur++.png", 300, 300);
        // Event Mouse
        //this.set_events(Gdk.EventMask.BUTTON_PRESS_MASK);
        this.connect("button-press-event", Lang.bind(this, function(widget, event) {
            /* NG
            https://people.gnome.org/~gcampagna/docs/Gdk-3.0/Gdk.Event.html
            
            if (event.type === Gdk.EventType.BUTTON_PRESS) {
                this.begin_move_drag(event.button, event.x_root, event.y_root, event.time);
            */
            if (event.get_event_type() === Gdk.EventType.BUTTON_PRESS) {
                let [res, x_root, y_root] = event.get_root_coords();
                this.begin_move_drag(
                    event.get_button()[1],
                    x_root,
                    y_root,
                    event.get_time()
                );
            } else if (event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS) {
                this.close();
            }
        }));
        // Event Draw
        this.connect("draw", Lang.bind(this, function(widget, cr) {
            Gdk.cairo_set_source_pixbuf(cr, this.pixbuf, 0, 0);
            cr.paint();
        }));
        // z-order
        this.set_keep_below(true);
        // etc
        this.set_decorated(false);
        this.resize(300, 300);
        this.show_all();
    }
});

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

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

desktop_naechan

あぁ、苗ちゃんがいつもデスクトップにいる。
カワイイよぉ、剥製にして飾…(嘘です

最近気が付いた、g_set_prgname でアクティビティ横の文字列を決められる。
たしか Ubuntu は違ったような、まあ猿専用ディストリなんてガン無視でいい。

そんなことより。
button-press-event ハンドラの仕様は何なんだよ!

Gdk.Event

UNION だとこういうバインドしか手段が無いのか、それとも別の理由か。
現状 Python, C のようにメソッドは使えず関数で取得しか手が無い。

ぶっちゃけ。
コレで混乱しなかったらこんな素敵なサイトなんて見つけられなかったぞ!
やはり何か具体的にアプリを作ってみないと解らない事って多い。