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

Gjs on Visual Studio Code

Fedora 28 でプログラミング。

っても GTK+ の大幅な変更は二年毎になったので 29 までメンテのみ。
半年毎にゴロッと変わる GTK3 初期の頃は今考えると結構楽しかった。

OpenJDK は 1.8 のまま、1.9 から jjs が es6 完全対応なんだが。
Gjs はプロファイラがどうたららしいけど、正直どうでもいい。
Python3 も 3.6 のまま、つーことで何も変化無し。
変わらなすぎて面白くないのは筆者だけなのだろうか。

特に書くこともないので終わり、では悲しいので。
Visual Studio Code で gjs アプリ(comipoli)の F5 実行でも。
長いので以下 vscode と略。

書くまでもなく”ウザい!邪魔!迷惑!”のコード保管は全部無効に。
Visual Studio Code 2018 | PaePoi
現在は更に以下を追加。

// 拡張機能の推奨なんて超迷惑
"extensions.ignoreRecommendations": true,
"extensions.showRecommendationsOnlyOnDemand": true

vscode を起動しウエルカムページから開発したいアプリのディレクトリを開く。
キーボードショートカットの設定を開く。
F5 で検索して Node.js の debug 実行のキーを Delete キーで殺す。
Node.js もやりたい人はデフォルトのキーにしてね、筆者はやらん。

.vscode/tasks.json は以下のように。

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Start Comipoli",
            "type": "shell",
            "command": "gjs",
            "args": [
                "./comipoli"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "presentation": {
                "focus": true,
                "reveal": "always",
                "panel": "new"
            }
        }
    ]
}


こうしておけば部品ファイルを開いている状態でも実行できて便利。
下に出るコンソールから Ctrl+C で強制終了もできる。
終了したら何かキーを叩けばコンソールは終了してくれる。

後はスニペットをガシガシ登録。
Ctrl+Space を先に打ち込む必要があるのがメンドイぞ!

{
	 "_connect": {
		"prefix": "con",
		"body": [
			"${1:Widget}.connect(\"${2:SignalName}\", ($3)=> {",
            "    $0",
            "});"
		],
		"description": "connect"
	},
	"_imports": {
		"prefix": "imp",
		"body": [
			"const ${1:GLib} = imports.gi.${1};$0"
		],
		"description": "imports"
	}
}

って。
おいおい Gedit より便利じゃなったじゃないか!
シェルスクリプトだけで拡張できる Gedit よりはるかに面倒だが。

でも vscode って思いっきり拡張子依存なのよね。
拡張子が無いファイルを扱うことが多い Linux ではチト困る。
それを補う Gedit のモードラインが再現できればなぁって感じ。

JXA: Image Resize and Save

今回は JXA にて画像のリサイズと保存。

(旧) Cocoaの日々: キャプチャ画像を縮小して保存する

Cocoa は他の OS のグラフィック(デバイス)コンテキストと違っていて
現在フォーカスがあるグラフィックコンテキストに描写する、という手法。
つまり NSImage に NSImage をリサイズして描写するってことみたい。

各画像フォーマットのデータを作成する

NSDictionary を作るのが面倒だなぁ。
変換で楽したいし NSImageCompressionFactor は調べると NSString だし。
ってことで。

ObjC.import("Cocoa");

console.log($.NSImageCompressionFactor.js);
//=> NSImageCompressionFactor

まんまカヨ!

クオリティは 1.0 が最高値みたい
筆者はいつものように GIMP に合わせて 0.85 にしてみる。
ということでこんなコードになりました。

#!/usr/bin/osascript

ObjC.import("Cocoa");

function run(argv) {
    let srcImage = $.NSImage.alloc.initWithContentsOfFile(argv[0]);
    // Create 300x200 Image
    let newImage = $.NSImage.alloc.initWithSize($.NSMakeSize(300, 200));
    newImage.lockFocus;
    $.NSGraphicsContext.saveGraphicsState;
    $.NSGraphicsContext.currentContext.setImageInterpolation($.NSImageInterpolationHigh);
    srcImage.drawInRectFromRectOperationFraction(
        $.NSMakeRect(0, 0, 300, 200),
        $.NSZeroRect,
        $.NSCompositeSourceOver,
        1.0);
    $.NSGraphicsContext.restoreGraphicsState;
    newImage.unlockFocus;
    // JPEG Write;
    let bmp = $.NSBitmapImageRep.imageRepWithData(newImage.TIFFRepresentation);
    bmp.alpha = false;
    let prop = $({"NSImageCompressionFactor": 0.85});
    let data = bmp.representationUsingTypeProperties($.NSJPEGFileType, prop);
    data.writeToFileAtomically($(`${argv[0]}_300x200.jpg`), true);
}

prop の値を変更すると画像のサイズと画質が変わるのが確認できた。
これでサムネイル選択の作成に取りかかれる、最大の難関みたいな気がするけど。

JXA: NSApplication argv

JXA で osacompile にて app 化すると argv が仕事しない件。
NSProcessInfo を使うことで解決した。

function run(argv) {
    $.NSApplication.sharedApplication;
    const window = new MyWindow();
    $.NSApp.setActivationPolicy($.NSApplicationActivationPolicyRegular);
    ObjC.registerSubclass({
        name: "AppDelegate",
        protocols: ["NSApplicationDelegate"],
        methods: {
            "applicationDidFinishLaunching:": {
                types: ["void", ["id"]],
                implementation: (notification)=> {
                    let arr = $.NSProcessInfo.processInfo.arguments.js;
                    if (arr.length > 2) window.setPath(arr[2].js);
                }
            }
        }
    });
    //
    // etc..
    //
    // Not work after compiling
    //if (argv.length < 0) window.setPath(argv[0]);
    $.NSApp.delegate = $.AppDelegate.new;
    $.NSApp.run;
}

引数の数に注意。
[“osascript”, “src”, parm1, param2, …]

これで js のままでも app にコンパイルしても両方仕事する。
スクリプトのまま使うなら argv を使ったほうが簡単だけどね。

ところで前回の zipinfo の件だけど。
macOS の zip は -U オプションすら使えないじゃん。
パッチが当たっていないどころか UNICODE サポートですらないってことだ。
ということでこいつの出力からの変換は不可能であるようだ。

で、zipdetails という perl のコマンドを見つけた。
こいつの出力からなんとかならないかな?
かなり意地悪な zip ファイルを用意して試す。

改行が UNOCODE 未対応のようで化ける、駄目だ。
でも perl でできるということはスクリプトで得られるということだ。
バイナリを見ると、単純にファイル名は構造体にそのまんま入っているヤン。

って、だから JavaScript じゃバイナリは直接扱えないんだってばさ!
UInt8Array に変換すると超激遅なのは Gjs で経験済み。
Objective-c での手段も見つからない、みんな C でやっているの?

もう macOS 版は日本語アーカイブをサポートしない、で終わりにしよう。

Gedit RepeatLine Plugin

筆者は macOS で Visual Studio Code を使っている。
しかし Fedora では Gedit を使い続けている。
Fedora でも併用しようと考えたけど結局 Gedit しか使わない。

しかし Visual Studio Code には便利すぎる機能がある。
opthon(alt)+shift+down で行の複製ができる、これが超スバラシイ。

fn+left
shift+fn+right
command+c
fn+right
return
command+v

とやっていたことを一発だ、よく使うんだな行の複製って。
ちなみに fn の所は TextEdit.app 同様に command でもいい。
筆者は US 配列なので fn のほうが楽だということで。

てか mac の日本語キーボードは何故 fn が右なのか、マジで糞。
US 配列を店頭でも普通に買えるようにしてくれないかなぁ。
それは今は関係なくて。

Gedit でも同じことがやりたいぞ。
ということで Plugin を探し、、、じゃなくて作る!
何年ぶりの新規プラグイン作りだろう、ワクワク。

#-*- coding:utf-8 -*-

#    Gedit repeat plugin version 3.22.0
#    Copyright © 2018 sasakima-nao <sasakimanao@gmail.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; _endher version 2 of the License, or
#    (at your option) any later version.

import gi, os
gi.require_version("Gtk", "3.0")
gi.require_version("Gedit", "3.0")
gi.require_version("Peas", "1.0")

from gi.repository import GObject, Gedit, Gtk, Gio, GLib

class RepeatLineAppActivatable(GObject.Object, Gedit.AppActivatable):
    """
        Set GMenu and Accelerator
    """
    app = GObject.Property(type=Gedit.App)
 
    def __init__(self):
        GObject.Object.__init__(self)
 
    def do_activate(self):
        # "<Alt><Shift>Down" Not Work
        self.app.add_accelerator("<Alt><Shift>d", "win.repeatline", None)
        self.menu_ext = self.extend_menu("tools-section")
        item = Gio.MenuItem.new("Repeat Line",  "win.repeatline")
        self.menu_ext.append_menu_item(item)
 
    def do_deactivate(self):
        self.app.remove_accelerator("win.repeatline", None)

class RepeatLinePlugin(GObject.Object, Gedit.WindowActivatable):
    __gtype_name__ = "RepeatLinePlugin"
    window = GObject.Property(type=Gedit.Window)
    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        self.action = Gio.SimpleAction.new("repeatline", None)
        self.action.connect('activate', self.on_repeatline_activate)
        self.window.add_action(self.action)

    def do_deactivate(self):
        self.window.remove_action("repeatline")

    def do_update_state(self):
        self.action.set_enabled(self.window.get_active_document() != None)

    def on_repeatline_activate(self, action, param):
        view = self.window.get_active_view()
        buf = view.get_buffer()
        _start_ = buf.get_start_iter()
        _end_ = buf.get_end_iter()
        spos = buf.props.cursor_position
        epos = spos
        it = buf.get_iter_at_offset(spos - 1)
        line = None
        while 1:
            # search line start position
            if it.equal(_start_) or it.get_char() == "\n":
                line = it.copy()
                it = buf.get_iter_at_offset(epos)
                break
            spos -= 1
            it = buf.get_iter_at_offset(spos)
        while 1:
            # search line end position
            if it.equal(_end_) or it.get_char() == "\n":
                s = line.get_text(it)
                if line.equal(_start_):
                    s = "\n" + s
                buf.insert(it, s, -1)
                break;
            epos += 1
            it = buf.get_iter_at_offset(epos)

作ってみた。

残念ながら Alt+Shift+Down は無視された。
Alt+Shift+D でいいやもう。

os.getenv(GEDIT_CURRENT_LINE)
が使えると思ったけどこれはプラグインからは参照できないのね。
しかたがないので GtkTextIter で地味に \n 位置を探すことに。

先頭と最後は \n が無いけどこんな処理でイケた。
何をやっているかは GtkTextBuffer のドキュメントで。

とりあえずこれで Gedit でも同様なことができるぞい。
探せば既にあるかもだけど、プログラミングは経験値だよと一言。

JXA: is Command Exist

Comipoli JXA 版はココにきて色々問題が出て困っている。
Fedora で作成した日本語入り cbz が開けなかったり…
コンパイルすると起動引数を受け付けないとか…
他色々…

間違いを堂々と書いていたり、私ってほんとバカ(古い
JXA NSOpenPanel | PaePoi
後で修正するのも面倒なので確実なものだけ書くようにしたい。

そういえば unrar や 7za がインストールしてあるか確認しないと。
JXA では、えっと。

#!/usr/bin/osascript

ObjC.import("Cocoa");

function isCommandExist(cmd) {
    let task = $.NSTask.new;
    let pipe = $.NSPipe.pipe;
    task.standardOutput = pipe;
    task.standardError = pipe;
    task.launchPath = "/usr/bin/type";
    task.arguments = $([cmd]);
    task.launch;
    task.waitUntilExit;
    return task.terminationStatus === 0;
}
console.log(isCommandExist("unzip"));
console.log(isCommandExist("unrar"));

これでいいか。

stdout, stderror を吐かないように pipe に入れて捨てている。
> /dev/null を配列にいれるとエラーになった。

ところで zipinfo -1 という素敵な手段を今頃知った。
unzip -Z -1 でもいい。

 if (/\.(cbz|zip)$/i.test(path)) {
    this.status = 0;
    this.namelist = [];
    //let output = this.getString(["unzip", "-Z", path]);
    let output = this.getString(["zipinfo", "-1", path]);
    let list = output.split("\n");
    for (let line of list) {
        if (PICEXT.test(line)) {
            this.namelist.push(line);
        }
        /*if (line.startsWith("-")) {
            let name = line.slice(53);
            if (PICEXT.test(name)) {
                this.namelist.push(name);
            }
        }*/
    }
    this.namelist.sort();
}

今まで何を。。。。。

Fedora も同様だった。
オリジナルも次はこれにしよう。