PyObjC」タグアーカイブ

import objc

今頃気が付いた。
PyObjC の AppKit に objc は含まれていた。

#!/usr/bin/env python3

#import objc
from AppKit import *

いらなかったんや!!!

PyObjC Tips ページを作っていて気がつく、書いていないのに動いたって。
作っているうちに又気がついたことがあったらコッチに書くわ、ネタがないし。
しかしゼロから作るのはどう書くか迷って中々進まない。

Python3 plistlib

plistlib — Mac OS X .plist ファイルの生成と解析 ? Python 3.7.2 ドキュメント

こんなモジュールが Python3 のデフォルトであったのか!
Info.plist の編集はどうやろうか迷ったけどコイツでいこう。

#!/usr/bin/env python3

import plistlib

PATH = 'Comipoli.app/Contents/Info.plist'

doctype = dict(
    CFBundleDocumentTypes = [
        dict(
            CFBundleTypeExtensions = ['cbz'],
            CFBundleTypeRole = 'Viewer',
            LSTypeIsPackage = False,
            NSPersistentStoreTypeKey = 'Binary'
        ),
        dict(
            CFBundleTypeExtensions = ['cbr'],
            CFBundleTypeRole = 'Viewer',
            LSTypeIsPackage = False,
            NSPersistentStoreTypeKey = 'Binary'
        ),
        dict(
            CFBundleTypeExtensions = ['cb7'],
            CFBundleTypeRole = 'Viewer',
            LSTypeIsPackage = False,
            NSPersistentStoreTypeKey = 'Binary'
        ),
        dict(
            CFBundleTypeExtensions = ['pdf'],
            CFBundleTypeRole = 'Viewer',
            LSTypeIsPackage = False,
            NSPersistentStoreTypeKey = 'Binary'
        )
    ]
)
plist = None
with open(PATH, 'rb') as fp:
    plist = plistlib.load(fp)

plist.update(doctype)

with open(PATH, 'wb') as fp:
    plistlib.dump(plist, fp)

make_plist.py

他のアプリの Info.plist を参考にこれだけ追加してみた。
アイコンを変更することもできるけどまあいいや。

ということで、ビルドスクリプトの最後でコレを実行。
/Applications に移動して pdf で二本指タップしてみる。

Comipoli.app が見事に登録されています。
選択すれば開くことができるしデフォルトアプリにすることもできる。

ところで、0.0.2 は app 化すると引数起動ができなかった。

(旧) Cocoaの日々: アプリ起動時に渡される引数の処理

App にすると application:openFiles: でしか引数を受け付けしないのね。
Window を作るのを applicationWillFinishLaunching: に変更。
application:openFiles: のほうが良さげなのでこっちで処理。
よし日本語でも問題なく引数付き起動できるようになった。

しかし PyObjC だけでここまで作れるとは自分でも思わなかった。
macOS でも Python3 は使うべき。

WebKit on PyGObject and PyObjC

WebKit バインドは PyObjC に含まれている。
Fedora の gir には WebKit2 が含まれている。
もしかしたら共通コードでイケるんでね?

#!/usr/bin/env python3

import objc
from AppKit import *
from WebKit import *

class WView(WKWebView):
    def init(self):
        config = WKWebViewConfiguration.new()
        objc.super(WView, self).initWithFrame_configuration_(NSZeroRect, config)
        return self

class Win(NSWindow):
    def init(self):
        frame = NSMakeRect(100, 400, 900, 600)
        objc.super(Win, self).initWithContentRect_styleMask_backing_defer_(
            frame,
            NSTitledWindowMask |
            NSClosableWindowMask |
            NSResizableWindowMask |
            NSMiniaturizableWindowMask,
            NSBackingStoreBuffered,
            False)
        self.setTitle_('Web')
        self.setDelegate_(self)
        # view
        self.webview = WView.new()
        self.webview.setFrameSize_(self.contentView().frame().size)
        self.contentView().addSubview_(self.webview)
        # url
        url = NSURL.URLWithString_('https://www.google.com/')
        req = NSURLRequest.requestWithURL_(url)
        self.webview.loadRequest_(req)
        return self

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

class Menu(NSMenu):
    def init(self):
        objc.super(Menu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        item_quit = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

NSApplication.sharedApplication()
window = Win.new()
window.makeKeyAndOrderFront_(window)
NSApp.setMainMenu_(Menu.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()

PyObjC

#!/usr/bin/env python3

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

class WebView(WebKit2.WebView):
    def __init__(self, ctx):
        WebKit2.WebView.__init__(self, web_context=ctx)

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        #
        ctx = WebKit2.WebContext()
        self.webkit = WebView(ctx)
        self.add(self.webkit)
        self.webkit.load_uri('https://www.google.com/')
        #
        self.resize(900, 600)
        self.show_all()

class App(Gtk.Application):
    __gtype_name__ = 'App'
    def __init__(self):
        GLib.set_prgname('App')
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)
    
    def do_activate(self):
        self.props.active_window.present()

App().run(sys.argv)

PyGObject

全然違ったwwwww
Apple さん、なんで URL は文字列じゃダメなの?
GNOME さん、WebKit2.WebView() ではエラーになるんですけど?

実は我がアプリに本棚機能を付けようかと考えて。
WebKit からローカル HTML でやれば GNOME, macOS 共通にできるかなって。
こりゃ無理だ。

Combine

python ファイルの合体スクリプトはわざわざ目印を用意する必要無いと気が付いた。
class から始まっている行から最後までを読み込みすればいい。
一部書き換えが必要だったけど下記に落ち着いた。

#!/usr/bin/env python3

import os

HEAD = '''#!/usr/bin/env python3

import objc, os, shutil, zipfile, re, subprocess
from AppKit import *
from Quartz.CoreGraphics import *

PICEXT = '\.(jpe?g|png|gif)$'
'''
FOOT = 'main()\n'

lines = [HEAD]
begin = False

for filename in [s for s in os.listdir() if s.startswith('comipoli_')]:
    with open(filename) as f:
        lines.append('# {}\n'.format(filename))
        for l in f.read().split('\n'):
            if begin:
                lines.append(l)
            elif l.startswith('class'):
                begin = True
                lines.append(l)
    begin = False

lines.append(FOOT)

with open('Comipoli.py', 'w') as f:
    f.write('\n'.join(lines))

こうなった。

後は pip3 で py2app を導入、py2applet はセットで付いてくる。

pip3 install -U py2app

ビルドスクリプトの作成。
前回のコードで icns を作る。
上記コードでソースファイルを一つに合体。
後は py2applet にこう渡すだけでアイコン付きの app を作ってくれる。
最後に片付け。

#!/bin/sh

# icns
python3 make_icns.py
iconutil -c icns comipoli.iconset

# combine
python3 make_combine.py

# build
py2applet --no-strip Comipoli.py comipoli.icns

# clean
rm -r comipoli.iconset
rm comipoli.icns
rm Comipoli.py

ただ
–no-strip オプションを付けているのは

Xcode インストール無しだとこんなダイアログが出るから。
気にせず [今はしない] を選んでも app は作れるけどね。

unrar はフルパス指定にしたらあっさり動いた。
そんなこんなでとっととサイト公開することに。

macOS アプリケーション – L’Isola di Niente

何か忘れている気がするけどまあいいや。

Create 72dpi Icon in Retina Display

JXA では手抜きをしたけど今回はキチンと ICON を作ろう。

icnsファイルの作り方(Mac) – 2番煎じMEMO

こんなに沢山の画像を作るなんて面倒だよ。
これも PyObjC で一つの画像からリサイズでやってしまおう。

画像のリサイズ保存方法は検索で簡単に見つかる。
しかし、Retina Display の mac では勝手に 144dpi になってしまう!
逆にそれを利用して 144dpi を作る、72dpi を自力で作る手段を探す。

NSImage をリサイズする。

なるほど、丸パクさせていただきます。
PyObjC では下記のように。

API Notes: Quartz frameworks ? PyObjC ? the Python ? Objective-C bridge

ということで。

#!/usr/bin/env python3

# make_icns.py
# This Program is Retina Display Mac Only

import os
from AppKit import *
from Quartz.CoreGraphics import *

# Preference
PNGFILE = 'icon.png'
ICONSET = 'comipoli.iconset'

src_image = NSImage.alloc().initWithContentsOfFile_(PNGFILE)
os.mkdir(ICONSET)
os.chdir(ICONSET)

def create_png(img, name):
    bmp = NSBitmapImageRep.imageRepWithData_(img.TIFFRepresentation())
    data = bmp.representationUsingType_properties_(NSBitmapImageFileTypePNG, {})
    data.writeToFile_atomically_(name, True)


for n in [512, 256, 128, 32, 16]:
    # 72dpi
    img = NSBitmapImageRep.imageRepWithData_(src_image.TIFFRepresentation()).CGImage()
    ctx = CGBitmapContextCreate(None, n, n, 8, 4 * n, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast)
    CGContextDrawImage(ctx, CGRectMake(0, 0, n, n), img)
    imgref = CGBitmapContextCreateImage(ctx)
    image72dpi = NSImage.alloc().initWithCGImage_size_(imgref, (n,n))
    create_png(image72dpi, 'icon_{0}x{0}.png'.format(n))
    # 144dpi
    image144dpi = NSImage.alloc().initWithSize_(NSMakeSize(n, n))
    image144dpi.lockFocus()
    NSGraphicsContext.saveGraphicsState()
    NSGraphicsContext.currentContext().setImageInterpolation_(NSImageInterpolationHigh)
    src_image.drawInRect_(NSMakeRect(0, 0, n, n))
    NSGraphicsContext.restoreGraphicsState()
    image144dpi.unlockFocus()
    create_png(image144dpi, 'icon_{0}x{0}@2x.png'.format(n))

で。

よし使える。

実はもう app 化は完成しているんだけど。
app にすると unrar にパスを通しても unrar を認識しない問題が出た。
そりゃ app は bashrc なんて読み込みしないよな、本来 macOS には無いし。