Visual Studio Code 2018

Visual Studio Code がなんだか変だ。
無効にしたはずの迷惑機能が復活している、設定が変わったか?
やり直ししよう。

{
    // ドットを打っても候補が表示されないように
    "editor.suggestOnTriggerCharacters": false,
     // クイック候補の無効化
    "editor.quickSuggestions": {
        "other": false,
        "comments": false,
        "strings": false
    },
    // かわりに tab キー保管を有効化
    "editor.tabCompletion": true,
    // 自動閉じ括弧保管にイライラする
    "editor.autoClosingBrackets": false,
    // モードラインのほうがいい!
    "editor.detectIndentation": false,
    //
    // これで Gedit と同じになる、後は好み
    // ちな Welcome ページは便利なので活用
    //
    // 右端で折り返す
    "editor.wordWrap": "on",
    // 全部タブで開く
    "window.openFilesInNewWindow": "off",
    // 再起動で直近のファイルを開かない
    "window.restoreWindows": "none",
    // ミニマップなんてウザい
    "editor.minimap.enabled": false,
    // 行の強調表示もウザい
    "editor.renderLineHighlight": "none",
}

ほんと mac 版 Gedit で日本語入力ができればこんなことしなくてもいいのに。
コード保管万歳な人は絶対にプログラミングなんてやっていないだろ!

でも Welcome ページって使ってみると便利だよね。
macOS は基本的にファイラーは使わないって方向ですし。
mac が使いづらいって人はファイルをダブルクリックで思考停止していると思う。
アプリで全部管理できればファイラーなんていらないもんね。

更に
task.json のバージョンを変更しようとしたら一部に波線が。

波線の上にカーソルを合わせるとヘルプがポップアップ。
相変わらずやたら親切で笑えるよ。

Tasks in Visual Studio Code

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

ものすごく多機能になったけどものすごく面倒臭いよ。
いや単機能でいいなら tasks の配列に入れなくてもいいんだけど。

筆者はアプリの設定をやりたいんじゃない、JXA を書きたいんだぃ!
ほんとシンプルだけど強力な Gedit みたくにはできないものか。

Comipoli for macOS

Safari からのアクセスが微妙に増えている気がする。
筆者は Fedora メインで mac はサブなので mac ネタなんてほとんど無いのに。
でもそういえば macOS も bash だし fedora よりユーザーは当然多いし。

Comipoli の macOS 版を JXA で作る計画は一応あるんだけど。
cbz を Finder でサムネイル表示できない macOS で意味あるんだろうか?
てか筆者は Cocoa の知識はほとんど無い。
しかも Objective-c から JavaScript へ変換という情報がほぼ無いことが必要。

そもそも NSWindow の配置はレイアウタなのか絶対位置なのかさえ知らない。
よし解らないなら実験だ、NSImageView を 2 つ置いて並べてみよう。

#!/usr/bin/osascript

ObjC.import("Cocoa");

const P_LEFT = $("/Users/sasakima-nao/Pictures/三嶋ゆらら.jpg");
const P_RIGHT = $("/Users/sasakima-nao/Pictures/優木苗.jpg");

ObjC.registerSubclass({
    name: 'WinDelegate',
    //superclass: 'NSObject',
    protocols: ['NSWindowDelegate'],
    methods: {
        // メソッド名は合体しない
        'windowDidResize:': {
            types: ['void', ['id']],
            implementation: function(aNotification) {
                fitImage();
            }
        },
        'windowWillClose:': {
            types: ['void', ['id']],
            implementation: function(notification) {
                return app.terminate(0);
            }
        }
    }
});

/**
 * func
 */
var fitImage = ()=> {
    let aw = window.frame.size.width;
    let ah = window.frame.size.height;
    let iw = leftView.image.size.width;
    let ih = leftView.image.size.height;
    // left
    let f = leftView.frame;
    f.size = $.NSMakeSize(aw/2, ah);
    leftView.frame = f;
    // right
    f = rightView.frame;
    f.size = $.NSMakeSize(aw/2, ah);
    f.origin = $.CGPointMake(aw/2, 0);
    rightView.frame = f;
}
var createImageView = (path)=> { 
    let image = $.NSImage.alloc.initWithContentsOfFile(path);
    let view = $.NSImageView.new;
    view.setImageScaling($.NSScaleToFit);
    view.setImage(image);
    return view;
}

/**
 * image
 */
leftView = createImageView(P_LEFT);
rightView = createImageView(P_RIGHT);

/**
 * Window
 */
let window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
    $.NSMakeRect(0, 0, 320, 400),
    $.NSTitledWindowMask
    | $.NSClosableWindowMask
    | $.NSMiniaturizableWindowMask
    | $.NSResizableWindowMask,
    $.NSBackingStoreBuffered,
    false
);
window.title = $("comipoli");
//window.center;
window.orderFrontRegardless;
window.delegate = $.WinDelegate.new;
window.contentView.addSubview(leftView);
window.contentView.addSubview(rightView);
window.makeKeyAndOrderFront(window);
fitImage();

/**
 * Application
 */
let app = $.NSApplication.sharedApplication;
app.setActivationPolicy($.NSApplicationActivationPolicyRegular);
app.mainMenu = function() {
    
    function newMenu(title, action, key) {
       return $.NSMenuItem.alloc.initWithTitleActionKeyEquivalent(title, action, key);
    }
    
    const appName = "Test Window";
    
    const mainMenu = $.NSMenu.new;
    const itemApp  = $.NSMenuItem.new;
    const menuApp  = $.NSMenu.new;
    itemApp.submenu  = menuApp;
    mainMenu.addItem(itemApp);
    menuApp.addItem(newMenu(`Quit ${appName}`, 'terminate:', 'q') );

    return mainMenu;
}();
app.run;

class 使いたい…

うん絶対位置配置なんだね。
macOS にも unzip はあるし意外に移植は簡単そうだ。
osacompile でアプリにすることもできるはず。

問題はコード分割が現状できないことなんだが。

Gmail…

google アカウントから「ブロックしました」と度々通知が来るようになった。
何だよ、筆者のアカウントなんかハッキングしてもたいしたもんは無いぞ?

何度も来るのでさすがにおかしいと思い通知の時間を元に色々検証。
って、その通知メールを受信している Sylpheed 自身が原因だった。
imap で既読を付けようとする毎にブロックされているようだ。
3.6 は Gmail が要求するセキュリティに合致していないってことかな。

Sylpheed – 軽快で使いやすいオープンソースのメールソフト

3.7 が出ているけどこの件には関係なさそうだなぁ。
安全性の低いアプリへのアクセスを許可もできるみたいだけど。。。。。
今は全アカウントで imap なので何を使ってもいいし。
他を探してもまたブロックされるかもしれないし。

ということで数年ぶりに Evolution を起動。
こいつはブロックされないようだ、当然だと思うけど。

あぁ、GNOME 純正でないのは KeePassX と google-chrome だけになってしまった。
実際 macOS ではこれに Visual Studio Code が入るだけで使ってるし。

十年くらい前に Linux を使い始めてからずっと使い続けているメーラーなのに。
まさかこんな理由で乗り換えするはめになるとはね。
今後もこんな感じでサードパーティの淘汰みたいな流れなんだろうな。

unzip escape

comipoli を gjs 化するので python3 の zipfile が使えない。
ということで unzip コマンドを使う方法に切り替えるのだが
展開する内部ファイル名にブラケット [, ] があると展開できないと気が付いた。

検索するとアスタリスクが駄目なことは皆気がついているようだ、他にもあるかな?
file-roller の GPL ソースを見るのが一番速い。

file-roller/fr-command-zip.c at master ? GNOME/file-roller ? GitHub

[]*?!^-\

こんなにあった、てか記号全部ではないのね。
駄目文字の前にバックスラッシュを付けるだけだし変換は簡単だね。

var ComipoliArchive = class ComipoliArchive {
    // etc...
    _zipEscape(str) {
        const ESCAPE = "[]*?!^-\\";
        let res = "";
        for (let s of str) {
            if (ESCAPE.includes(s)) res += "\\";
            res += s;
        }
        return res;
    }
    getItem(num) {
        let cmdArray = null;
        let pixbuf = null;
        switch (this.status) {
        case 1:
            cmdArray = ["unrar", "p", "-inul", "-@", "--", this.path, this.namelist[num]];
            break;
        case 2:
            cmdArray = ["7za", "x", "-so", this.path, this.namelist[num]];
            break;
        default:
            //cmdArray = ["unzip", "-pj", this.path, this.namelist[num]];
            cmdArray = ["unzip", "-pj", this.path, this._zipEscape(this.namelist[num])];
        }
        let sp = Gio.Subprocess.new(cmdArray, Gio.SubprocessFlags.STDOUT_PIPE);
        let stream = sp.get_stdout_pipe();
        try {
            pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
        }
        catch(e) {
            print("Error: " + this.namelist[num] + "\n" + e);
        }
        stream.close(null);
        return pixbuf;
    }
}

展開できた。

しかし cairo 描写に書き換えたら 3D で動く毎に draw シグナルが…
これでは clutter_actor_save_easing_state が使えないや。

yield @ Python3 and JavaScript

我が comipoli はどうせ cairo に書き換えるなら Gjs 化してしまおう。
ということで以前 ClutterImage 作成が遅過ぎでお蔵入りしたソースを引っ張りだした。
ES6 仕様に書き換えをしていて yield でハマったので覚書。

#!/usr/bin/env python3

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

class Dlg(Gtk.Dialog):
    def __init__(self):
        Gtk.Dialog.__init__(self, transient_for=None, title="Comipoli Thumbnail")
        self.show_all()
        # idle
        self.iter = self.iter_func()
        GLib.idle_add(self.iter_idle)

    def iter_idle(self):
        try:
            n = next(self.iter)
            print(n)
            return True
        except Exception as e:
            return False

    def iter_func(self):
        for num in range(100):
            yield num

dlg = Dlg()
dlg.run()
dlg.destroy()

PyGObject

#!/usr/bin/gjs

const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;

var Dlg = GObject.registerClass({
    GTypeName: "Dlg"
}, class Dlg extends Gtk.Dialog {
    _init() {
        super._init({
            transient_for: null,
            title: "Comipoli Thumbnail"
        });
        this.show_all();
        // idle
        this.iter = this.iter_func();
        GLib.idle_add(GLib.PRIORITY_LOW, ()=> {
            let r = this.iter.next();
            if (r.done) return false;
            print(r.value);
            return true;
        });
    }
    * iter_func() {
        for (let i=0; i<100; i++) {
            yield i;
        }
    }
});
Gtk.init(null);
let dlg = new Dlg();
dlg.run();
dlg.destroy();

Gjs

g_idle_add の引数が違うことは置いておいて。
g_idle_add は何も処理を行っていない時に与えられた関数を実行し続ける関数。
false を戻さないかぎり動き続ける。

Python は例外で判別、JavaScript は next メソッドの戻り値で判別。
yield, next の使い方はまったく同じなので混乱してもーたがね。

検索すると個別で next() を呼んでいる人ばかりで困ったよ。
どう考えても yield でそんな使い方はしないと思うんですけど。
複数ファイル処理で一つ読み終わったらとっとと表示みたいな場合が主だよね。

あと JavaScript のジェネレーター関数はアスタリスクが必要。
無名関数にする手段は無いっぽい、JavaScript を筆者が使うようになった切っ掛けなのに。
でも Python には無名関数はいらないな、そんなの Python じゃない。
適材適所ってことですよ、何でもアリはコードを読み辛くするだけだ。