PyObjC」タグアーカイブ

M1 Mac JXA and PyObjC

会社の都合で休日出勤は無しなので M1 Air をしっかり試す。
たしかパワーは 2019 Air の五倍なので筆者の 2018 からだと八倍くらいか。
ログイン画面まで 15 秒、パスワードを打ち込みプラス 15 秒でもう使える。
他に Web でベタ褒めされている件、あれらは全部本当だった。
こんなの筆者の知っている Macbook Air じゃない、怪物にも程がある。

とりあえず再構築、Atom を公式サイトから入れよう。
起動しようとすると Rosetta2 をインストールしますか?とアラートが。
インストールを選ぶと一瞬で入ったから内部で有効化しただけだと思うけど。
ぶっちゃけ来年くらいまでは全員が入れると思う。

Atom を Gedit のように使う – Paepoi

再構築完了、書いてて良かった自前カスタムのすべて。
早速 JXA を試そう、ES2020 は対応されているか。
?? 演算子と BigInt と globalThis をまとめてテスト。

#!/usr/bin/osascript

const p = globalThis.print ?? console?.log;
p(12341234123412341234n *2n);

atom_run

動くじゃないの。

GNOME 3.38 Gjs | Paepoi Blog
import 関数は使えない。
Promise も動いた、ただ筆者には便利さが解らない。
matchAll も動く、Gjs と同じく詳細は出力されないけど。

だいたい Gjs と同じって感じ。
ただし、NSMakeRect のバグはそのまんまだ、やっぱりやる気無いのね。

しかしマジで Python2 がデフォルトで入っているのがなんとも。
PHP は 7.3.22 か、さくらインターネットでさえ 7.4.10 なのに。
perl と ruby はどうでもいい。

端末で python3 と打ち込むと CommandLineTools のダイアログ。
インストールすると Python3 が使える、3.8.2 だ。
普通に使えるけど、PyObjC が pip3 でインストールできない。

Python Release Python 3.9.1 | Python.org

macOS 64-bit universal2 installer
というものが公式にあった、これならイケるかな。
CommandLineTools を先に入れないと 3.8 に上書きされるので注意。
インストーラに従うだけで 3.9 にアップグレードできる。
んで、pip3 のアップグレードを行う必要があった。

# pip upgrade
pip3 install --upgrade pip
# install
pip3 install -U pyobjc

PyObjC Tips – Paepoi

PyObjC

PyObjC もコレで問題なく動くじゃないの。
少し面倒だったけど混迷期だからしかたがないね。

PyObjC Exif

よし休日だ。
今日こそ、今度こそは五条川でカワセミを見つけて撮影してやるぞ!
と昼前に遊歩道へ行って十分後。

こんなにあっさり。
わざわざ八田川まで通っていた日々は何だったんだろう。
まあいい、次は狩りの瞬間を撮りたいな。

ところで。

Fedora の場合 Exif 情報を得るには GExiv2 を使えばいい。
macOS の場合はどうすればいいんだろう、ちゃちゃっと検索。

@macosx: NSImage Exif (metadata)

CGImageSource なんてものが SDK にあるみたい。
連想配列を使うのなら PyObjC で書くとスゲェ簡単だぞ。
ImageIO SDK の Objective-c Bridge は以下にあった。

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Quartz

ということでエラー処理を省いて PyObjC で書き換えてみた。
メソッドの場合は引数の数だけアンダーバーが必要なのを忘れずに。

#!/usr/bin/env python3

from AppKit import *
from Quartz.ImageIO import *
import sys

def get_exif_dict(filename):
    url = NSURL.fileURLWithPath_(filename)
    source = CGImageSourceCreateWithURL(url, None)
    data = CGImageSourceCopyPropertiesAtIndex(source, 0, None)
    return data #['{Exif}']

d = get_exif_dict(sys.argv[1])
print(d)

以上。

macos_exif

dict の中に更に {ExifAux} 等の dict があって細かく情報が得られる。
CFDictionaryRef も Python の dict 同様に添字でアクセスできる。
こんなに楽チンなのに PyObjC って全然流行らないのは何故だろう。

レンズの名前まで記録しているのね、こういう情報が残っていると助かる。
野鳥撮影は楽しいよ、プログラミングの楽しさになんか似ている。

macOS Get UTI

Apple 関連で開発をしていると UTI を調べる必要がある場合が多々ある。

Uniform Type Identifier – Wikipedia

Uniform Type Identifier Concepts

検索をしていたら素敵なページを見つけた。

Mac や iOS でファイルの種類を表す識別子 Uniform Type Identifiers を拡張子から調べる(Swiftで1行で出来る) – niwatakoのはてなブログ

てゆーか JXA でも PyObjC でもできる。
しかし困ったことに JXA では CFString が NSString にキャストできない。

objective c – JXA: Accessing CFString constants from CoreServices – Stack Overflow

上記を見つけてようやく解決。
console.log って C 言語の char[] を出力できる、初めて知った。
UTF16LE に変換は不要、CJK 文字列でも問題ないようです。

#!/usr/bin/osascript -l JavaScript

let jp = $('スズキ GSX250R').UTF8String;
console.log(jp);
//=> スズキ GSX250R

// ex: ft=js.jxa

ということで JXA にて簡単に調べるコマンドを作ってみる。

#!/usr/bin/osascript -l JavaScript

ObjC.import('CoreServices');

function run(argv) {
    for (let ext of argv) {
        let uti = $.UTTypeCreatePreferredIdentifierForTag(
            $.kUTTagClassFilenameExtension, $(ext), null);
        let s = $.CFStringGetCStringPtr(uti, 0);
        console.log(`${ext}: ${s}`);
    }
}

// ex: ft=js.jxa

getuti.js

せっかくなので基底タイプも調べたいぞ。
JXA で得る方法が解らなかったので PyObjC で書いてみる。
PyObjC は CFDictionary や CFString も Python の型と等価なので超簡単。
他の言語を使うのが馬鹿馬鹿しくなってしまうので注意が必要。

#!/usr/bin/env python3

import sys, CoreServices

for ext in sys.argv[1:]:
    uti = CoreServices.UTTypeCreatePreferredIdentifierForTag(
        CoreServices.kUTTagClassFilenameExtension, ext, None)
    arr = CoreServices.UTTypeCopyDeclaration(uti)['UTTypeConformsTo']
    con = ','.join(arr)
    print(f'{ext}: {uti} [{con}]')

# ex: ft=py

@PyObjC

getuti という拡張子の無い名前で +x のパーミッションを付けパスの通った場所へ。

getuti

JXA でやりたかったけどまだまだ修行が足りない。

注意: macOS 13 Ventura からは上記が使えません。
macOS: Get UTI (Ventura) | Paepoi Blog

NSView Auto Layout

NSView のレイアウト方式いろいろ – ひ?to?り?go?と

こんなの見つけた。
windowDidResize に頼らず NSView を引き延ばす手段がこんなにあったんだ。

autoresizingMask の指定
addConstraints でアンカーの指定
layout のオーバーライド

一つづつ作って試すの面倒だから NSView の上に NSView を置いて。
上記三つを全部 PyObjC でやってみた。

#!/usr/bin/env python3

from AppKit import *

RECT = ((0, 0), (300, 100))
wins = []

class TopView(NSView):
    def initWithFrame_(self, rect):
        objc.super(TopView, self).initWithFrame_(rect)
        return self

    def drawRect_(self, rect):
        # 全体にバッテンを描く
        NSColor.darkGrayColor().set()
        path = NSBezierPath.bezierPath()
        path.setLineWidth_(10)
        path.moveToPoint_((0, 0))
        path.lineToPoint_(rect.size) # タプルなのでこれでいい
        path.moveToPoint_((0, rect.size.height))
        path.lineToPoint_((rect.size.width, 0))
        path.stroke()

class SecondView(NSView):
    def initWithFrame_(self, rect):
        objc.super(SecondView, self).initWithFrame_(rect)
        self.v = TopView.alloc().initWithFrame_(RECT)
        self.addSubview_(self.v)
        return self

    def layout(self):
        # Override
        objc.super(SecondView, self).layout()
        self.v.setFrameSize_(self.frame().size)

class ThirdView(NSView):
    def initWithFrame_(self, rect):
        objc.super(ThirdView, self).initWithFrame_(rect)
        self.v = SecondView.alloc().initWithFrame_(RECT)
        self.addSubview_(self.v)
        #
        # addConstraints
        #
        self.v.setTranslatesAutoresizingMaskIntoConstraints_(False)
        self.addConstraints_([
            self.v.leftAnchor().constraintEqualToAnchor_constant_(self.leftAnchor(), 0),
            self.v.rightAnchor().constraintEqualToAnchor_constant_(self.rightAnchor(), 0),
            self.v.topAnchor().constraintEqualToAnchor_constant_(self.topAnchor(), 0),
            self.v.bottomAnchor().constraintEqualToAnchor_constant_(self.bottomAnchor(), 0)
        ])
        return self

class MyWindow(NSWindow):
    def init(self):
        objc.super(MyWindow, self).initWithContentRect_styleMask_backing_defer_(
            RECT,
            NSTitledWindowMask | NSClosableWindowMask |
            NSResizableWindowMask | NSMiniaturizableWindowMask,
            NSBackingStoreBuffered, False)
        self.center()
        self.setTitle_('Auto Resize')
        #self.setDelegate_(self)
        # View
        self.v = ThirdView.alloc().initWithFrame_(RECT)
        self.contentView().addSubview_(self.v)
        #
        # autoresizingMask
        #
        self.v.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable) # 2+16
        #
        return self

    #def windowDidResize_(self, sender):
    #    self.v.setFrameSize_(self.contentView().frame().size)

class AppDelegate(NSObject):
    def applicationDidFinishLaunching_(self, notification):
        window = MyWindow.new()
        window.makeKeyAndOrderFront_(window)
        wins.append(window)

class AppMenu(NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        # quit menu
        item_quit = NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

NSApplication.sharedApplication()
NSApp.setMainMenu_(AppMenu.new())
NSApp.setDelegate_(AppDelegate.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()

なるほど、全部 PyObjC からでも使えますね。
でも結局 windowDidResize が一番扱いやすいような。。。。。

NSPopUpButton, NSComboBox

NSComboBox を使ってみたけど何か変だ。
システム環境設定のコンボボックスと形が違う。
タップでプルダウンも矢印の所しか反応しないし何コレ?

Views and Controls | Apple Developer Documentation
どうやらこれは NSPopUpButton というコントロールのようだ。
GTK+ みたいに Gallery が欲しいんですけど。
Widget Gallery: GTK+ 3 Reference Manual

NSComboBox が NSTextField のサブクラス。
NSPopUpButton が NSButton のサブクラス。

items = ['YAMAHA', 'SUZUKI']

class MyView(NSView):
    def initWithFrame_(self, rect):
        objc.super(MyView, self).initWithFrame_(rect)
        #
        # NSComboBox @ NSTextField init method
        cbox = NSComboBox.labelWithString_('Select')
        w, h = cbox.frameSize() # Get Control Height
        cbox.setFrame_(((10, 10), (200, h)))
        cbox.addItemsWithObjectValues_(items)
        self.addSubview_(cbox)
        #
        # NSPopUpButton
        pop = NSPopUpButton.alloc().initWithFrame_pullsDown_(((10, 40), (200, h)), False)
        pop.addItemsWithTitles_(items)
        self.addSubview_(pop)
        #
        return self

未選択状態が必要な場合は NSComboBox を利用。
みたいな使い分けでいいのかな?
どこをタップしてもいい NSPopUpButton はタッチ操作向けだよな。
もう少し弄ってみる。