JXA」タグアーカイブ

JavaScript filter

最近 JavaScript をまったくやっていないことに気がついた。
ES2023 とかどうなっているんだろう、全然知らないので検索。

JavaScriptのES2023・ES2022の新機能まとめ – ICS MEDIA

jxa

JXA てか JavaScriptCore は ES2023 対応だ、まあ Safari が対応済ですし。
Gjs は駄目だった、おいおい Spider Monkey さん。
ECMA Script を定義しているのって Mozilla さんなのに。

しかし配列の非破壊操作か、Tips を書くときに短くできるメリットはあるけど。
いや大規模な Web アプリとかなら必要になるのかもな。

配列を征する者はJSを制す。JavaScriptのスマートな配列操作テクニック – ICS MEDIA

filter は便利そうですね。
というか Python の内包表記みたいに使えるんでないの。

#!/usr/bin/env python3

ls = ['aa.js', 'bb.py', 'cc.js', 'dd.png', 'ee.js', 'ff.json']

# *.js を抜き出す
jss = [s for s in ls if s.endswith('.js')]
print(jss)

python

#!/usr/bin/gjs

let ls = ['aa.js', 'bb.py', 'cc.js', 'dd.png', 'ee.js', 'ff.json'];

// *.js を抜き出す
let jss = ls.filter(s => s.endsWith('.js'))
console.log(jss)

JavaScript

なんだ便利じゃん、今度からコレ使おう。
あと関係ないかもだけど。

log

Gjs でも console.log が使えるようになっていた、知らなかった!
以前から使えた log とは少し出力が違うのね、いや基本 print を使うけど。

Image conversion @ PyObjC and JXA

前回書いた検索で簡単に見つかる画像変換ですが。
Objective-c での公式は以下に。

Viewing, Editing, and Saving Images in an Image View

PyObjC でしたら以下がありますね。

[PyObjC CoreGraphics API Example] Shows how to use the functional API of the CoreGraphics framework through PyObjC and Python 2.7. #pyobjc #python #coregraphics #quartz #macos #examples ? GitHub

ただし Python2 コードですし kUTTypeJPEG はとうの昔に非推奨です。
Python3 にて UniformTypeIdentifiers.UTTypeJPEG を使うよう書き換え。

System-declared uniform type identifiers | Apple Developer Documentation

したんだけど何度やってもエラー、原因が解らず困っていた。
本日原因が判明した、こんな理由だったとは。

#!/usr/bin/env python3

import UniformTypeIdentifiers, LaunchServices

print(type(LaunchServices.kUTTypeJPEG))
print(type(UniformTypeIdentifiers.UTTypeJPEG))

''' output
<class 'objc.pyobjc_unicode'>
<objective-c class _UTCoreType at 0x1e3f02bd0>

'''

まさかの。
PyObjC の kUTTypeJPEG は単なる文字列だった。

UTTypeJPEG は普通にポインタ、そりゃエラーになるよと。
ということで PyObjC ではこんなコードになります。
破棄は Python にまかせないと二重破棄でエラー。

#!/usr/bin/env python3

from AppKit import *
from Quartz import *
from UniformTypeIdentifiers import *

src = NSURL.fileURLWithPath_('panaleica.heic')
dest = NSURL.fileURLWithPath_('panaleica.jpg')

isr = CGImageSourceCreateWithURL(src, None)
image = CGImageSourceCreateImageAtIndex(isr, 0, None)

destimg = CGImageDestinationCreateWithURL(dest, str(UTTypeJPEG), 1, None)
if destimg:
    CGImageDestinationAddImage(destimg, image, None)
    res = CGImageDestinationFinalize(destimg)
    print(res)
    #CFRelease(destimg) # Error
else:
    print('error')

HEIF や WebP からでも JPEG に変換できる。
ただし iPhone 写真等の回転情報は継承されず横向きになるので注意。

rotate

いやオートメーションで使うなら AppleScript か JXA にしないと。
こちらでは UniformTypeIdentifiers が使えないので直接文字列で。

#!/usr/bin/osascript

ObjC.import('Cocoa');
ObjC.import('Quartz');
//ObjC.import('UniformTypeIdentifiers');

let src = $.NSURL.fileURLWithPath('gara.webp');
let dest = $.NSURL.fileURLWithPath('gara.heic');

let isr = $.CGImageSourceCreateWithURL(src, null);
let image = $.CGImageSourceCreateImageAtIndex(isr, 0, null);

//let destimg = $.CGImageDestinationCreateWithURL(dest, $('public.jpeg'), 1, null); // jpeg
//let destimg = $.CGImageDestinationCreateWithURL(dest, $('public.png'), 1, null); // png
let destimg = $.CGImageDestinationCreateWithURL(dest, $('public.heic'), 1, null);
if (destimg) {
    $.CGImageDestinationAddImage(destimg, image, null);
    let res = $.CGImageDestinationFinalize(destimg);
    console.log(res);
} else {
    console.log('error');
}

後は同じですね。
PyObjC を先にやらなかったら文字列ということに気が付かなかったかも。
HEIF にもコレで変換可能でした。

#!/usr/bin/osascript

// webp is bad

ObjC.import('Cocoa');
ObjC.import('Quartz');

let src = $.NSURL.fileURLWithPath('panaleica.heic');
let dest = $.NSURL.fileURLWithPath('panaleica.webp');

let isr = $.CGImageSourceCreateWithURL(src, null);
let image = $.CGImageSourceCreateImageAtIndex(isr, 0, null);

let destimg = $.CGImageDestinationCreateWithURL(dest, $('org.webmproject.webp'), 1, null);
if (destimg) {
    $.CGImageDestinationAddImage(destimg, image, null);
    let res = $.CGImageDestinationFinalize(destimg);
    console.log(res); // false
} else {
    console.log('error');
}

ただ WebP から変換はできるけど WebP へはどちらでも変換できなかった。
多分 Objective-c や Swift でも、public.*** しか駄目なのだろうか?
一番やりたいことなのに、もう少し調べます。

ところで、こんなことをやって今頃知ったんですけど。
UTTypeCreatePreferredIdentifierForTag が廃止になっています。

UTTypeCreatePreferredIdentifierForTag | Apple Developer Documentation

筆者が書いた UTI を調べるコマンドが使えなくなっていて焦った。
というか UTI を調べるコードを書いているブログは全滅だ、わーい!
上記で使おうとして動かなかった、新しい方法を探さなくちゃ。

Dinamic Import: Gjs and JXA

現在の JavaScript はダイナミックインポートが可能。
下記ページで Chrome や Safari で動くと解りますね。

全モダンブラウザで使えるJavaScriptのdynamic import(動的読み込み) – Qiita

GNOME の Gjs は imports 関数があるので関係ないんだけど。
そういえば macOS の JXA はファイル分割ができないのが困る。
コレを利用してクラス毎に分割できるかな。

#!/usr/bin/osascript

import('./sub.js')
	.then((module) => {
        console.log('Start!');
		const sub = new module.Sub();
        sub.subMethod();
	});

main.js

export class Sub {
    subMethod() {
        console.log('this is Sub Class Method');
    }
}

sub.js

osa

なんでだよ!
Promise だと認識はしているけど動作しないってことみたい。
JavaScriptCore が全部やってくれるってわけじゃないんかい。

Gjs でも同じかな?
console.log を print に書き換えて実験。

#!/usr/bin/gjs

import('./sub.js')
	.then((module) => {
		const sub = new module.Sub();
        sub.subMethod(); 
	});

main.js

export class Sub {
    constructor(app) {
        print('Hello');
    }
    subMethod() {
        print("this is Sub Class Method");
    }
}

gjs

動いたのでコンストラクタも入れてみた、普通に Class ですね。
いや Gjs は imports を使ったほうが簡単ですけど。
というか、Apple は内製スクリプトには本当にヤル気が無いなって。

macOS Monterey JXA

macOS Monterey の JXA は NSRect のバグが無くなった。
ということで、今の知識で NSWindow を作ってみたらどうなるか。

いや PyObjC のほうが簡単なんだけど、デフォルトで入っていないのが。
JXA ならどんな Mac でもそのまま動かせるという利点があるので捨て難い。

ただ JXA では PyObjC みたいに class にできないんだよなぁ。
ネットで簡単に見つかる方法では全部グローバル変数にするしかないのが。
そもそも JavaScript の class は他の言語の class とは違うし。
なので GNOME の Gjs もアクロバットな手段で class っぽくしている。

ObjC.registerSubclass を上手く利用してソレっぽくやってみよう。
追加メソッドはどう書けばいいのかな?とか。

#!/usr/bin/osascript

ObjC.import('Cocoa');

//let wins = [];

ObjC.registerSubclass({
    name: 'AppDelegate',
    protocols: ['NSApplicationDelegate'],
    methods: {
        'applicationDidFinishLaunching:': function (notification) {
            let window = $.MyWindow.alloc.initWithContentRectStyleMaskBackingDefer(
                $.NSMakeRect(0, 0, 300, 100),
                $.NSTitledWindowMask
                | $.NSClosableWindowMask
                | $.NSMiniaturizableWindowMask
                | $.NSResizableWindowMask,
                $.NSBackingStoreBuffered,
                false
            );
            window.makeKeyAndOrderFront(window);
            $.NSApp.activateIgnoringOtherApps(true);
            //wins.push(window);
        }
    }
});

ObjC.registerSubclass({
    name: 'WinDelegate',
    protocols: ['NSWindowDelegate'],
    methods: {
        'windowWillClose:': function(notification) {
            console.log('MyApp Close !');
            return $.NSApp.terminate(0);
        }
    }
});

ObjC.registerSubclass({
    name: 'MyWindow',
    superclass: 'NSWindow',
    propertyies: {},
    methods: {
        'initWithContentRect:styleMask:backing:defer:': function
        (contentRect, style, backingStoreType, flag) {
            let _this = ObjC.super(this).initWithContentRectStyleMaskBackingDefer(
               contentRect, style, backingStoreType, flag);
           _this.title = $('JXA NSWindow');
           _this.delegate = $.WinDelegate.new;
           // Button
           let button = $.NSButton.buttonWithTitleTargetAction('button', this, 'onButtonClick:');
           button.setFrame($.NSMakeRect(10, 10, 200, 36));
           _this.contentView.addSubview(button);
           // return
            return _this;
        },
        'onButtonClick:': {
            types: ['void', ['id']],
            implementation: function(sender) {
                sender.setTitle('Clicked!');
            }
        }
    }
});

/**
 * Application
 */
$.NSApplication.sharedApplication;
$.NSApp.setActivationPolicy($.NSApplicationActivationPolicyRegular);
$.NSApp.mainMenu = function() {
    let mainMenu = $.NSMenu.new;
    let itemApp  = $.NSMenuItem.new;
    mainMenu.addItem(itemApp);
    let menuApp  = $.NSMenu.new;
    itemApp.setSubmenu(menuApp);
    // quit menu
    let itemQuit = $.NSMenuItem.new;
    itemQuit.initWithTitleActionKeyEquivalent('Quit App', 'terminate:', 'q');
    menuApp.addItem(itemQuit);
    return mainMenu;
}();
$.NSApp.setDelegate($.AppDelegate.new);
$.NSApp.run;

nswindow

こんな感じになった、
ボタンのハンドラは delegate ではなく this に届くようだ。
オーバーライドは types 指定不要なのね、ふむふむ。
これだけ解れば応用でなんとかなりそう。

ただ PyObjC と違って NSWindow がガベージコレクションされないんだが。
let 指定ならばハンドラを抜けたら破棄されるはずなんだけど、何故だ?
Python とは破棄対象の選定方法が違うんだろう、知らんけど。

ところで Monterey で今頃気がついたんだけど。

power

iOS みたいに充電の保留機能が付いたんだね。
筆者は滅多に持ち歩かないんで意味はないかもだがけど。

macOS Monterey

macOS を Monterey に遅ればせながらアップグレード。
Fedora とは違ってスッゲェ時間が掛かると解っているので休日まで待った。
12.13GB か、前の 14GB より減ったとはいえ酷い。

そろそろ全部終わったかな?
って画面を表示させると「インストールします」ダイアログ。
ガックリ。。。。。

[続ける]-[同意] って感じでボタンを押さないとインストールが始まらない。
今度こそ終わったかな?って見ると「再起動」ダイアログ。
ションボリ。。。。。

その再起動が又時間が掛かる。
終わった!と思ったらフェイントだった。
ゲンナリ。。。。。

M1 でコレだぞ、Intel Air だったらドンダケだったんだ?
アップグレードくらいボタン一発でスパッとやってくださいよ。
あーやっとこさ終わった。

それはいいとして。

Apple、macOS 12 MontereyにPython 2.7.18などを同梱してリリース。Big Surまで同梱されていたphpは削除されるので注意を。

マジで PHP は削除されるのね。
httpd.conf が初期化されるのはいつものこと。

php

WordPress の時代にコレって macOS を Web 開発に使うなってメッセージ?
Fedora のように dnf コマンドで即座に導入できるってわけでもないのにさ。
いったいどんな政治的なやりとりがあったのか、みたいな。
これだから Fedora も併用しなきゃなんだよ、macOS で開発は自由が無い。

自力で Python3 を入れている人は残る。
3.10 になっているのか、アップグレードしとこ。
macOS 64-bit unibersal2 installer で M1 ネイティブだよ。

Python Release Python 3.10.0 | Python.org

Lightroom は問題なく動く、コレのために Mac を使っているのだし。

それと Fedora から SSH てか SFTP で簡単アクセスする用の
~/.ssh/id_rsa.pub
とか設置していたものも普通に残っているしアクセスもできる。

そんなことより何より。

#!/usr/bin/osascript

ObjC.import('Cocoa');
let r = $.NSMakeRect(0, 0, 10, 10);

console.log(`width = ${r.size.width}`);

jxa

JXA での NSRect バグがやっと、ついに修正されたぞ!
Big Sur アップデート毎にコレ動かしてションボリしてたのよね。
待ちわびた、待ちすぎて PyObjC 万歳になっちゃった。