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

JXA FileList

今回は JXA でディレクトリ内容列挙でも。
まあ Objective-c での手段は検索で山ほど見つかる、しかも日本語で。
Gjs だと英語しか見つからないのに、まあ macOS と比べてもね。

変換するだけとはいえ落とし穴は幾つかある。

#!/usr/bin/osascript

ObjC.import("Cocoa");

nil = $();

let path = $("~/Desktop").stringByExpandingTildeInPath;
let nsFiles = $.NSFileManager.defaultManager.contentsOfDirectoryAtPathError(path, nil);
//$.NSLog("%@", nsFiles);
let jsFiles = nsFiles.js; // NSArray to JS Array
for (let i=0; i<jsFiles.length; i++) {
    console.log(jsFiles[i].js);
}

nil は null ではない。
そういえばデルヒャァがそうだったなぁ、もう完全に忘れたけど。

NSArray のままではループできない。
こいつの変換も *.js メソッドでイケるのか、なるほど。

次は再起のように中身まで辿っていく手段。

#!/usr/bin/osascript

ObjC.import("Cocoa");

let path = $("~/Documents").stringByExpandingTildeInPath;
let nsDirEnum = $.NSFileManager.defaultManager.enumeratorAtPath(path);
//while (let name = nsDirEnum.nextObject) {
while (true) {
    let name = nsDirEnum.nextObject;
    if (name.isNil()) break;
    let fullPath = path.stringByAppendingPathComponent(name);   // Full Path
    console.log(fullPath.js);
}
console.log("done");

if (name == $()) では認識してくれない。
つまり検索でよく見つかるコメントアウト部分は使えない。
isNil() でなんとか JavaScript の boolean 値だと認識するようだ。

Gjs や PyGObject と違って型が明確に分離している。
ってもソレさえ理解できてしまえば日本語情報があるだけ簡単だね。

どうでもいいけど Gedit は日本語入力できないまま使っている。
開き直って日本語を使わなければいいのさ、ガッハッハ!
まあ JXA 専用だね。

ClutterBoxLayout

ClutterStage が現在 Wayland で使えないのはあきらめて。
当面は GtkClutter を使ったサンプルコードを書いていこう。

今回は ClutterActor のレイアウトマネージャ。

GTK+ と全然違い property の layout-manager で指定するようだ。
デフォルトは ClutterFixedLayout になっている。
書くまでもなく絶対値配置なので子 Actor は重なって表示される。

GtkBox のように並べて配置するには ClutterBoxLayout を指定。
clutter_actor_add_child するだけで普通に並んでいく。

#!/usr/bin/gjs

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

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

    _init: function() {
        this.parent({
            title: "add_actor"
        });
        // BoxLayout
        let layout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.VERTICAL,
            spacing: 1
        });
        // Actor
        this.actor = new Clutter.Actor({
            layout_manager: layout
        });
        // add
        let girls = ["椎名心実です", "あかね!", "くおえうえーーーうえうぅぅぅ"];
        for (let i=0; i<girls.length; i++) {
            let item = new Clutter.Text({
                x_align: Clutter.ActorAlign.START,
                x_expand: true,
                text: girls[i]
            });
            this.actor.add_child(item);
        }
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.actor);
        this.add(embed);
        // this
        this.connect("hide", Gtk.main_quit);
        this.show_all();
    }
});

GtkClutter.init(null);
new ListTest();
Gtk.main();

GListModel を使ってバインドもできる。
頻繁に入れ替えを行う場合はこちらのほうが便利かもしれない。

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const GtkClutter = imports.gi.GtkClutter;
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const Lang = imports.lang;

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

    _init: function() {
        this.parent({
            title: "GListStore"
        });
        // GListStore  
        this.model = new Gio.ListStore({
            item_type: Clutter.Text
        });
        // layout
        let layout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.VERTICAL,
            //pack_start: true,
            spacing: 1
        });
        // Actor
        this.actor = new Clutter.Actor({
            background_color: Clutter.Color.from_string("#aaa")[1],
            layout_manager: layout
        });
        this.actor.bind_model(this.model, Lang.bind(this, function(item) {
            return item;
        }));
        // append
        let girls = ["椎名心実です", "あかね!", "くおえうえーーーうえうぅぅぅ"];
        for (let i=0; i<girls.length; i++) {
            let item = new Clutter.Text({
                x_align: Clutter.ActorAlign.START,
                x_expand: true,
                text: girls[i]
            });
            this.model.append(item);
        }
        // remove
        this.model.remove(1);
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.actor);
        this.add(embed);
        // this
        this.connect("hide", Gtk.main_quit);
        this.resize(this.actor.width, this.actor.height);
        this.show_all();
    }
});

GtkClutter.init(null);
new ListTest();
Gtk.main();

GTK+ 同様に子 Actor のサイズによって親のサイズが拡大されるようだ。
ただし GTK+ 部品にまでは適用されないのでそこらは自力で。

ClutterStage on Wayland

GNOME 3.22 の Wayland 環境では ClutterStage で Window が作れない!

#!/usr/bin/env gjs

const Clutter = imports.gi.Clutter;

Clutter.init(null);
let stage = new Clutter.Stage({
    title: "TestWindow",
    background_color: Clutter.Color.from_string("#663322")[1]
});
stage.connect("hide", Clutter.main_quit);
stage.set_size(300, 200);
stage.show();
Clutter.main();

Window にならないヤン!

てか起動してもアクティブにならないし最初は見えないしetc…
アプリケーションメニューは普通に使えるし例外も吐かないんだけど。
Wayland はどういう認識をしているんだろう?

GNOME on Xorg でログインすれば普通に Window になる。
とはいえ通常なら GtkWindow に乗せて使うから致命的な問題ではないんだが。
サンプルコードを書くのに困るくらいだな。

ついでに GNOME on Xorg だと Gedit で Ctrl+F9 等のキーが使えるんだね。
これが Wayland だと動作しないのが筆者は微妙に不便。

Gedit External Tools

Gedit で外部ツールからスクリプト実行を筆者はよく使う。
今まで ContentType で振り分けしていた。
しかしコレでは gjs, jjs や Python, Python3 で振り分けできない。

考えてみたらシバンで実行すればいいじゃん。
+x パーミッションを付けない状態でもシバンを読取りすればいいのだし。
先頭に #! があればソレで、無いなら今までどおりにすればいいかな。

#!/bin/sh

# シバンがあるならソレで起動、無いなら ContentType 判別

h=`head -n1 $GEDIT_CURRENT_DOCUMENT_PATH`
if [[ $h = \#\!* ]]; then
    app=${h#*\!}
    echo $app $GEDIT_CURRENT_DOCUMENT_PATH
    $app $GEDIT_CURRENT_DOCUMENT_PATH
else
    echo @ContentType $GEDIT_CURRENT_DOCUMENT_TYPE
    if [[ $GEDIT_CURRENT_DOCUMENT_TYPE = *python ]]; then
        python3 $GEDIT_CURRENT_DOCUMENT_PATH
    elif [[ $GEDIT_CURRENT_DOCUMENT_TYPE = *javascript ]]; then
        gjs $GEDIT_CURRENT_DOCUMENT_PATH
    elif [[ $GEDIT_CURRENT_DOCUMENT_TYPE = *shellscript ]]; then
        sh $GEDIT_CURRENT_DOCUMENT_PATH
    elif [[ $GEDIT_CURRENT_DOCUMENT_TYPE = *vala ]]; then
        vala $GEDIT_CURRENT_DOCUMENT_PATH
    elif [[ $GEDIT_CURRENT_DOCUMENT_TYPE = *csrc ]]; then
        src=`head $GEDIT_CURRENT_DOCUMENT_PATH`
        case $src in
            *gtk.h*)
                echo "Gtk Build"
                gcc $GEDIT_CURRENT_DOCUMENT_PATH `pkg-config --cflags --libs gtk+-3.0` ;;
            *glib.h*)
                echo "GLib Build"
                gcc $GEDIT_CURRENT_DOCUMENT_PATH `pkg-config --cflags --libs glib-2.0` ;;
            *)
                echo "Gcc Build"
                gcc $GEDIT_CURRENT_DOCUMENT_PATH ;;
        esac
    else
        echo Non Support File
    fi
fi

これでもし他のスクリプト言語に目覚めてもシバンだけで対応できるぞ!
無いと思うけど。

Python __getitem__

自作 comicbook archive ビューアがまだまだ気に入らない。
ページが多い場合にとにかく遅い、理由は解っているけど。
全ページをメモリに展開し更にリサイズして配列に格納していたらそりゃ遅い。

マルチスレッド化とか色々試したけど Python じゃ無理だ。
サムネイル表示時にもう一度リサイズするとかなんか無駄なことしているし。

ページめくり高速化の為に全部読み込んでいたけど都度読み込みのほうが良さげ。
ということでクラスを作ったけど今まで配列から取り出ししていたんだよなぁ。
全部の処理を関数に書き換えするのが面倒臭いし間違えるのが怖い。

いや、コイツは Python で作っている。
__getitem__ を使えばいいじゃないか。

#!/usr/bin/env python3

from gi.repository import GLib, Gio, GdkPixbuf
import os, zipfile

class ComipoliArchive:
    def __init__(self):
        self.status = 0
        self.namelist = []

    def new_cba(self, uri, is_unrar, is_7za):
        self.arc = Gio.file_new_for_uri(uri)
        self.path = self.arc.get_path()
        ext = os.path.splitext(self.path)[1].lower()
        if ext == ".cbz":
            with zipfile.ZipFile(self.path) as o:
                l = o.namelist()
                l.sort()
                for name in l:
                    if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
                        self.namelist.append(name)
        elif is_unrar and ext == ".cbr":
            self.status = 1
            result, output, error, status = GLib.spawn_command_line_sync('unrar vt -p- -- "{0}"'.format(self.path))
            lines = output.decode("utf-8").split("\n")
            for line in lines:
                s = line.lstrip()
                if s.startswith('Name: '):
                    name = s[6:]
                    if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
                        self.namelist.append(name)
        elif is_7za and ext == ".cb7":
            self.status = 2
            result, output, error, status = GLib.spawn_command_line_sync('7za l -slt "{0}"'.format(self.path))
            lines = output.decode("utf-8").split("\n")
            for line in lines:
                if line.startswith('Path'):
                    name = line[7:]
                    if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
                        self.namelist.append(name)
        else:
            return 1
        return 0

    def __getitem__(self, num):
        stream = None
        if (self.status == 0):
            with zipfile.ZipFile(self.path) as o:
                data = o.read(self.namelist[num])
                stream = Gio.MemoryInputStream.new_from_data(data)
        elif (self.status == 1):
            sp = Gio.Subprocess.new(["unrar", "p", "-inul", "-@", "--", self.path, self.namelist[num]], Gio.SubprocessFlags.STDOUT_PIPE)
            stream = sp.get_stdout_pipe()
        elif (self.status == 2):
            sp = Gio.Subprocess.new(["7za", "x", "-so", self.path, self.namelist[num]], Gio.SubprocessFlags.STDOUT_PIPE)
            stream = sp.get_stdout_pipe()
        p = GdkPixbuf.Pixbuf.new_from_stream(stream)
        stream.close()
        return p

    def __len__(self):
        return len(self.namelist)

イケるやん!

これで今まで配列から GdkPixbuf を取り出ししていたように都度展開だ!
この機能はどんな時に使うんだ?と思っていたけどこういう場合なのね、ふむふむ。
yield なんていらなかったんや!まあ勉強になったしイイけど。

どうでもいいけど beta11 は arcive と誤字していた、次で直す。