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

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 の仕様をなんとかしてくれないかなぁ。

ClutterGst 3.0 Get Media Width, Height

スマホやタブレット PC の動画再生は常にフルスクリーン。
ソースの原寸や倍率なんてナンセンス。

で、今や QuickTime, Totem 等のデフォルトプレイヤーはそんな感じに。
だけどまだタブレット PC の普及は進んでいないんですよ。

Y901x beta1 で等倍や二倍を消してしばらく使ってみたけどしっくりこない。
やはり等倍等にする機能は付けることにする。
さて、ClutterGst ではどうやってソースサイズを得るのか。

ClutterGstPlayer: Clutter Gst 3.0.18 Reference Manual

clutter_gst_player_get_video_sink
にて ClutterGstVideoSink 構造体を得ることができる。

Clutter とあるけどチト試すと GstVideoSink と同じように扱えるようだ。
だとすれば C, Python のコードが Web 上に山程ある、もちろん英語で。
ソレらを JavaScript に変換すればよさそう。

つか Y901x 1.1 はズバリだ、早速変換してみよう。
又パラメータや戻り値が違うかもしれないから慎重に。

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

    _init: function(app) {
        this.parent({application: app});
        // var
        this.src_width = 0;
        this.src_height = 0;
        //
        // etc...
        //
        this.aspectratio = new ClutterGst.Aspectratio();
        this.player = new ClutterGst.Playback();
        // Ready Signal
        this.player.connect("ready", Lang.bind(this, function(player) {
            // Get Media About
            let vsink = player.get_video_sink();
            let it = vsink.iterate_pads(); // it @ GstPad Iteraror
            it.foreach(Lang.bind(this, function(pad) {
                let caps = pad.get_current_caps();
                let struct = caps.get_structure(0);
                this.src_width = struct.get_int("width")[1];
                this.src_height = struct.get_int("height")[1];
            }));
        }));
        //
        // etc...
        //
        this.show_all();
    }
});

ビックリするほど同じだった。
解説は Python Gst 関連のもっと詳しいサイトで、もちろん英語。

foreach にアレ?と思うかもだがコレ gst_iterator_foreach ですんで。
コイツも Gjs ではメソッドになるので Lang.bind にて this を親に置き換える。
つか Lang.bind ってネストできたのか、試しに書いて上手くいっただけだが。

原寸は得ることができたけどウインドウのリサイズはどうしよう?
gtk_widget_set_size_request は縮小できなるので使えない。

Y901x 1.1 は親ウインドウをリサイズでまかなっていた。
しかし今度のは GtkHeaderBar がある、内寸と外寸が一致しない。
うーん駄目元で中の GtkBox との差分を計算してリサイズしてみよう。

        this.change_video_size = function(n) {
            let w = Math.round(this.src_width * n);
            let h = Math.round(this.src_height * n) + SEEKBAR_HEIGHT;
            let diff_x = this.get_allocated_width() - this.vbox.get_allocated_width();
            let diff_y = this.get_allocated_height() - this.vbox.get_allocated_height();
            this.resize(w + diff_x, h + diff_y);
        };

ちなみに this のメソッドには Lang.bind はいらない。

この関数に 1 を渡すと、見事に原寸の動画が再生できた。
計算方法は意外にもコレでいいようだ、GtkHeaderBar よくワカンネエ!

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
*/

Clutter @ Mouse Drag

Y901x beta は Clutter でシークバーを作っている。
最初は詰まりまくった、なんたって実際に利用するアプリに使うのは初めて。

普通に button-press-event を使ったら全然動作しなかったし。
実際 clutter_actor_set_reactive 関数を見つけるまで手段に超苦しむことに。

やはり ClutterCanvas で書き込もうとも考えた、昔ながらの方法。
でもそれ OpenGL である Clutter 上で Cairo を使うってことだ、変だよ。
しかも何故かエラー出まくり、cairo_t 引数が null になるしワケワカメ。

何か手段は無いかと試行錯誤してまさかの結論が出た。
そういえば Drag and Drop 操作はシークバーと同じ動作だと思いついた。
マウスで掴んで動かして離す、これはイケそう。

ClutterDragAction: Clutter Reference Manual

おぉ、ClutterAction にはこんなものがあるじゃないか。
コレでトラフ画像の ClutterActor を動かせばシークバーとして使えそう。
引数から X 値を得て横方向だけ動くようにすれば従来どおりの手段が使える。
と思ったけど…

#!/usr/bin/gjs

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

const ClWin = new Lang.Class({
    Name: 'ClWin',
    Extends: Clutter.Stage,

    _init: function() {
        this.parent();
        this.connect("hide", Lang.bind(this, function() {
            Clutter.main_quit();
        }));
        let actor = new Clutter.Actor();
        actor.set_background_color(Clutter.Color.new(255, 0, 0, 255));
        actor.set_size(50, 50);
        this.add_child(actor);
        // DnD
        let dgaction = new Clutter.DragAction();
        //dgaction.connect("drag-begin", Lang.bind(this, function(action, actor, event_x) {
        //    print(event_x);
        //}));
        dgaction.connect("drag-motion", Lang.bind(this, function(action, actor, delta_x) {
    		//actor.set_position(delta_x, 0);
            print(delta_x);
        }));
        //dgaction.connect("drag-end", Lang.bind(this, function(action, actor, event_x) {
        //    print(event_x);
        //}));
        actor.add_action(dgaction, true);
        actor.set_reactive(true);

        this.set_size(300, 150);
        this.show_all();
    }
});

Clutter.init(null);
new ClWin();
Clutter.main();

clutter_dnd

ハンドラ指定しただけで ClutterActor 自体がドラッグされてしまうジャン!
しかも動かすだけなら drag-motion のみ処理すればよかった。
これってそういう Action だったのNE!

更に引数の delta_x 値はドラッグされている ClutterActor 上の値だった。
つまり変化無し、これじゃシークバーなんかに使えないっつーの。
今回は無駄骨だったけどいつかこの経験が役に立つ、といいな。

だけど、コレをやったおかげで clutter_actor_set_reactive 関数を見つけた。
上記リンク先の Description に普通に書いてあったという。

this.is_drag = false;
let actor = new Clutter.Actor();
actor.connect("button-press-event", Lang.bind(this, function(actor, event) {
    this.is_drag = true;
    let [x, y] = event.get_coords();
    print("x= " + x + "; y= " + y);
}));
actor.set_reactive(true); // !!!

あら動いた、シグナルを有効にするにはやはり必須なのね。

motion-notify-event が無いのも困った。
motion-event だと Drag していなくても反応する、回避は簡単だが。

actor.connect("motion-event", Lang.bind(this, function(actor, event) {
    if (this.is_drag) {

これでなんとかシークバーに利用する用の値は得ることができる。

さて実際の実装方法だ。
ClutterActor は特定領域の塗り潰しができないっぽい、Canvas は使いたくない。
ならば別色の ClutterActor をもう一つ用意して被せれば、俺って天才だ。
せっかくだからデジタル表示も被せちゃえ、ClutterText は背景透過だし。

event.x を得て親の横幅で割った比率を被せた ClutterActor の横幅に適用。
という我ながら大雑把な手段なのに理想どおりに動いた。
しかもその値を ClutterGst にも適用できるというオマケ付きで。

それにしても GUI アプリを作るのって本当に楽しいですね。
思ったとおりに動いてくれた時の嬉しさが CUI の比じゃない。

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 環境は頗る安定しています。